/***********************************************************************************

  Program Name: pcwcommon.js

  System      : PCW JS Library

  Function    : Common utility library, default functionality for all maintenances

  History     : Date        Description                                 Inits
                14/07/2003  New program                                 SPB
                23/07/2004  Added Hotkeys and Hotkey navigation         SPB

***********************************************************************************/

// Functionality flags
var DISABLE_SUBMIT_CONTROL    = false;
var DISABLE_POPUP_CONTROL     = false;
var ROW_HIGHLIGHT             = window.ROW_HIGHLIGHT        != undefined ? ROW_HIGHLIGHT        : true;
var ROW_ONE_TOUCH_SELECT      = window.ROW_ONE_TOUCH_SELECT != undefined ? ROW_ONE_TOUCH_SELECT : true;
var COLOUR_ROW_HIGHLIGHT      = window.COLOUR_ROW_HIGHLIGHT != undefined ? COLOUR_ROW_HIGHLIGHT : "#FFCC66";
var ENABLE_HOTKEYS            = true;
var ENABLE_HOTKEY_NAVIGATION  = true;
var ENABLE_DESKTOP_ALERTS     = window.ENABLE_DESKTOP_ALERTS    != undefined ? ENABLE_DESKTOP_ALERTS    : true;
var DISABLE_COLUMN_DRAGGING   = window.DISABLE_COLUMN_DRAGGING  != undefined ? DISABLE_COLUMN_DRAGGING  : false;
var DEFAULT_LINE_HEIGHT       = window.DEFAULT_LINE_HEIGHT      != undefined ? DEFAULT_LINE_HEIGHT      : 23;
var DEFAULT_ICON_WIDTH        = window.DEFAULT_ICON_WIDTH       != undefined ? DEFAULT_ICON_WIDTH       : 16;
var DEFAULT_ICON_HEIGHT       = window.DEFAULT_ICON_HEIGHT      != undefined ? DEFAULT_ICON_HEIGHT      : 16;
var DEFAULT_ICON_SPACING      = window.DEFAULT_ICON_SPACING     != undefined ? DEFAULT_ICON_SPACING     : 4;
var NAVIGATOR_ICON_SIZE       = window.NAVIGATOR_ICON_SIZE      != undefined ? NAVIGATOR_ICON_SIZE      : 20;
var TREEITEM_CHECKED_IMAGE      = "Checked";   // These constants need moving into TreeView object properties
var TREEITEM_UNCHECKED_IMAGE    = "UnChecked"; // then they need removing
var TREEITEM_UNSELECTABLE_IMAGE = "ReadOnly";

var PREFIX = "";
// Global variables
var _globalCommonPopupList    = [];
var _globalCommonPopupIndex   = [];
var _globalMenuProgs          = "";
var _globalMenuBarList        = [];
var _globalDataGridList       = [];
var _globalObjectList         = [];
var _developer                = window.location.href.indexOf("eodev1sb") != -1;

// Constants
var INVALID_HANDLE                = "INVALID_HANDLE";
var MSG_MAIN_CREATE_POPUP         = 1;
var MSG_MAIN_CREATE_DATAGRID      = 2;
var MSG_MAIN_CREATE_POPUP         = 3;
var MSG_MAIN_CREATE_DATECOMBO     = 4;
var MSG_MAIN_CREATE_CALENDAR      = 5;
var MSG_MAIN_CREATE_TREEVIEW      = 6;
var MSG_MAIN_CREATE_MENUBAR       = 7;
var MSG_MAIN_CREATE_WAITBOX       = 8;
var MSG_MAIN_CREATE_DATAGRID      = 9;
var MSG_MAIN_REFRESH_ROLLOVERS    = 10;
var MSG_MAIN_CREATE_ROWHIGHLIGHT  = 11;
var MSG_MAIN_CREATE_HOTKEYS       = 12;
var MSG_MAIN_PROCESS_HTML         = 13;
var MSG_MAIN_DESKTOP_ALERT        = 14;
var MSG_MAIN_CALLBACK             = 15;
var MSG_MAIN_REFRESH_DATAGRID     = 16;

// Return values from onNeedData event
var DATAGRID_DATA_READY       = 1;
var DATAGRID_DATA_PENDING     = 2;

// Status message display options
var DATAGRID_STATUS_DELAY_FADE            = true;
var DATAGRID_HIDE_BORDER                  = 1;
var DATAGRID_FIXED_HEIGHT                 = 2;
var DATAGRID_NO_SORT                      = 4;
var DATAGRID_ENABLE_SELECTION             = 8;
var DATAGRID_ENABLE_MULTI_SELECT          = 16;
var DATAGRID_RENDER_INLINE                = 32;
var DATAGRID_NO_COLUMN_DRAG               = 64;
var DATAGRID_SCROLL_X                     = 128;
var DATAGRID_SCROLL_X_AUTO                = 256 + 128;
var DATAGRID_SCROLL_Y                     = 512;
var DATAGRID_SCROLL_Y_AUTO                = 1024 + 512;
var DATAGRID_SCROLL_ALL                   = DATAGRID_SCROLL_X + DATAGRID_SCROLL_Y;
var DATAGRID_SCROLL_AUTO                  = DATAGRID_SCROLL_X_AUTO + DATAGRID_SCROLL_Y_AUTO;
var DATAGRID_SCROLL_X_ALLOW_HEIGHT_ADJUST = 2048;

// Functionality constants
var DATAGRID_IMMEDIATE_REDRAW       = true;

var NAVIGATOR_RENDER_INLINE = 1;
var NAVIGATOR_HIDE_BUTTONS  = 2;

// Global messages
var MSG_OBJECT_BACKGROUND_COLOUR = 9000;
var MSG_OBJECT_SET_COORDS        = 9001;
var MSG_OBJECT_INIT_MOUSE_EVENTS = 9002;
var MSG_THREAD_COMPLETE          = 10000;
var MSG_THREAD_BUSY              = 10001;

// Object options
var DESKTOPALERT_USE_COOKIE     = 1;
var DESKTOPALERT_NO_POSITIONING = 2;

var DESKTOPALERT_TYPE_EMAIL    = 0;
var DESKTOPALERT_TYPE_INFO     = 1;
var DESKTOPALERT_TYPE_ERROR    = 2;
var DESKTOPALERT_TYPE_WARNING  = 3;
var DESKTOPALERT_TYPE_SPOOL    = 4;
var DESKTOPALERT_TYPE_DOWNLOAD = 5;
var DESKTOPALERT_TYPE_LINKPROG = 6;

var TREEVIEW_SHOW_HEADER      = 1;
var TREEVIEW_OVERFLOW_HIDDEN  = 2;
var TREEVIEW_FORCE_SELECTION  = 4;
var TREEVIEW_ENABLE_DRAG_DROP = 8;
var TREEVIEW_HIDE_BORDER      = 16;

var ERROR_OK                     = 0;

// Private properties
var fBody                     = null;
var fDesktopAlert             = null;
var fDoc                      = document;
var fHTML                     = null;
var fHasFocus                 = true;
var fHotKeys                  = null;
var fIE                       = document.all != undefined;
var fLastFocusedObject        = null;
var fPopupActive              = false;
var fProcessingPopup          = false;
var fReportFrame              = null;
var fRollovers                = null;
var fThis                     = this;
var fWin                      = window;
var Cx                        = null;
var fStopLoad                 = false; // Used to prevent load going any further

// Public properties
var shiftKey                = false;
var ctrlKey                 = false;
var hotKeys                 = null;

// System properties - library use only (Do not use for anything, including reading)
var container               = null;

// Public methods
function exportData(pClientRecordLimit) {  doExportData(pClientRecordLimit); }

// Public events
function onHotKey(pSender, pKeyCode) {};
function onSendChunkComplete() { alert("Default send chunk complete callback called") };


/* ------------------------------------------------------------------------------
   Constructor
   ------------------------------------------------------------------------------ */
// Create the contex
Cx = new Context(window.location.href.split("?")[0], window);

// If context is library context and this is not the index page, reload this
// page into the index page
if(Cx.lib == Cx.win && (Cx.win.CHILD_PROG || (Cx.win.location.href.indexOf("/mpcwindexu10") == -1 && Cx.win.location.href.indexOf("/mpcwviewe22") == -1 && Cx.win.location.href.indexOf("/mpinhefditp10") == -1 && Cx.win.location.href.indexOf("/freequery.html") == -1))) {

  // Force this page into the index page and load all the libraries
  if(Cx.lib.reloadPage) Cx.lib.reloadPage(window.location.href);

  // reloadPage library function is not available, 
  // so last attempt is to force this page into the index page manually
  else {
    Cx.win.location.replace("mpcwindexu10.htm?_p=" + encodeURIComponent(Cx.win.location.href));
  }

  // Prevent this page's load getting any further
  fStopLoad = true;
}
  
if(!fStopLoad) {
  var log = new Cx.lib.Object();
  log.register(Cx);

  // Object inheritance
  var fApplication = new Cx.lib.MessageObject("Application Message Queue", 20);
  fThis = fApplication;

  // Main message loop, controlled by the MessageObject class
  fApplication._main = doMain;
}

/* ------------------------------------------------------------------------------
   Implementation
   ------------------------------------------------------------------------------ */
function Context(pName, pWindow) {
  var fThis = this;
  
  this.name = pName.indexOf("mpcwindexu10") != -1 ? "Library Context" : "Content Context";
  this.url  = pName;
  this.doc  = pWindow.document;
  this.win  = pWindow;
  this.lib = (this.win.parent && this.win.parent.Cx) ? this.win.parent.Cx.lib : this.win;
  this.context = null;
  this.parentContext = null;
  this.objectRegistry = [];
  this.popupActive = false;
  this.childContexts = [];
  
  // Auto register with a parent context
  if(this.win.parent && this.win.parent.Cx) {
    this.win.parent.Cx.childContexts.push(this);
    this.parentContext = this.win.parent.Cx;
  }
  
  this.free = doDestroy;
  
  this.getEvent = doGetEvent;
  this.getWindowWidth = doGetWindowWidth;
  this.getWindowHeight = doGetWindowHeight;
  
  function doDestroy() {
    for(var i=this.childContexts.length-1;i>=0;i--) {
      try {
        this.childContexts[i].free();
      }
      catch(e) {
        // Ignore any "Already freed" errors
      }
    }
    this.childContexts.length = 0;
    
    if(this.parentContext != null) {
      for(var i=this.parentContext.childContexts.length-1;i>=0;i--) {
        if(this.parentContext.childContexts[i] == this) {
          this.parentContext.childContexts.splice(i, 1);
          break;
        }
      }
    }
    
//    if(_developer) confirm("freeing " + this.objectRegistry.length + " object(s) from the '" + this.name + "' context.");
//    var test = "";
    for(var i=this.objectRegistry.length-1;i>=0;i--) {
      this.objectRegistry[i].free();
//      test += this.objectRegistry[i].constructor.toString().split("(")[0].split(" ")[1] + (this.objectRegistry[i].name ? "(" + this.objectRegistry[i].name + ")" : "") + "\n";
      this.objectRegistry.length = i;
    }
//    if(_developer) confirm("Freed:\n" + test);
    
    this.doc = null;
    this.win = null;
    this.lib = null;
    this.context = null;
    this.parentContext = null;
    pWindow = null;
    
  }
  
  function doGetEvent(e) {
    return fIE ? this.win.event : e;
  }
  
  function doGetWindowHeight(pContext) {
    return fThis.doc.body.clientHeight;
  }


  function doGetWindowWidth(pContext) {
    return fThis.doc.body.clientWidth;
  }  
  
  function doAttachChild(pContext) {
    if(pContext.parentContext == null) {
      pContext.parentContext = this;
      this.childContexts.push(pContext);
    }
  }
}

function reloadPage(pUrl) {
  var program, params, path;
  var sessionInfo = [];
  
  // Extract URL sections
  program = pUrl.split("?")[0];
  params  = (pUrl + "?").split("?")[1];
  
  // Remove any server information so that program runs in this domain
  temp = program.split("/");
  program = temp.pop();
  path = temp.join("/");
  if(path != "") path += "/";
  
  // Extract session information from the parameters
  params = params.split("&");
  for(var i=params.length-1;i>=0;i--) {
    switch(params[i].split("=")[0].toUpperCase()) {
      case "S":
      case "M":
      case "L":
      case "P":
        sessionInfo.push(params.splice(i, 1));
        break;
    }
  }
  
  // Encode the parameters
  if(params.length == 0) {
    params = "";
  }
  else {
    params = "%3F" + encodeURIComponent(params.join("&"));
  }

  // Set the url, ensuring it runs within the index page
  Cx.lib.location.replace(path + "mpcwindexu10.htm?_p=" +  program + params + "&" + sessionInfo.join("&"));
}

function doMain(pMsg, pMsgData) {
  var returnValue = MSG_THREAD_COMPLETE;
  
  switch (pMsg) {
    case MSG_MAIN_PROCESS_HTML:       enablePopupControl(pMsgData);   break;
    case MSG_MAIN_CREATE_POPUP:       createPopupLink(pMsgData);      break;
    case MSG_MAIN_CREATE_DATAGRID:    createDataGrid(pMsgData);       break;
    case MSG_MAIN_CREATE_DATECOMBO:   createDateCombo(pMsgData);      break;
    case MSG_MAIN_CREATE_CALENDAR:    createInlineCalendar(pMsgData); break;
    case MSG_MAIN_CREATE_TREEVIEW:    createInlineTreeView(pMsgData); break;
    case MSG_MAIN_CREATE_MENUBAR:     createMenuBar(pMsgData);        break;
    case MSG_MAIN_CREATE_WAITBOX:     createWaitBox(pMsgData);        break;
    case MSG_MAIN_REFRESH_ROLLOVERS:  fRollovers.refresh(pMsgData);   break;
    case MSG_MAIN_CREATE_HOTKEYS:     doHotKeyHooking();              break;
    case MSG_MAIN_DESKTOP_ALERT:      doDesktopAlert(pMsgData);       break;
    case MSG_MAIN_CALLBACK:           pMsgData();                     break;
    case MSG_MAIN_REFRESH_DATAGRID:   doRefreshDataGrid(pMsgData);    break;
  }
  
  return returnValue;
}

function encodeIt(pValue) {
  var returnValue = "";
  
  for(var i=0;i<pValue.length;i++) {
    returnValue += "%" + Number(pValue.charCodeAt(i)).toString(16);
  }
  
  return returnValue;
}

// Get method of reporting
function doExportData(pClientRecordLimit) {
  var frm = "";
  var frmElements = null;
  var URI = "";
  var tpe = "";
  
  // Check if there is a datagrid on here...
  if(_globalDataGridList.length == 0) return false;
  
  // Get rid of any # anchor marks
  URI = window.location.href.split("#")[0];
  frm = document.getElementById("mainForm");
  frmElements = frm.getElementsByTagName("INPUT");

  for(var i=0;i<frmElements.length;i++) {
    tpe = frmElements[i].type.toUpperCase();
    if(tpe == "SUBMIT" || tpe == "RESET" || tpe == "NONE") continue;
    
    if(",G,focusField,focusFieldType,fi_rr,fi_lr,fi_fr,fi_directory,".indexOf("," + frmElements[i].name + ",") == -1) {
      if(tpe == "CHECKBOX" || tpe == "RADIO") {
        if(frmElements[i].checked) {
          URI = URI.addUrlParam(frmElements[i].name, frmElements[i].value);
        }
        else {
          URI = URI.addUrlParam(frmElements[i].name, "");
        }
      }
      else {
        URI = URI.addUrlParam(frmElements[i].name, frmElements[i].value);
      }
    }
  }
  
  // Add in any active data grid filter
  if(_globalDataGridList[0].filter != null) {
    var filterParams = _globalDataGridList[0].filter.split("&");
    URI = URI.addUrlParam("fi_directoryid", filterParams[0]);
    filterParams.shift(0);
    if(filterParams.length != 0) URI += "&" + filterParams.join("&");
  }
  
  // Add in schedule request flag if required
  if(_globalDataGridList[0].totalResults >= pClientRecordLimit) {
    URI = URI.addUrlParam("schedule", "yes");
    info(Cx.lib.EO_REPORT_SCHEDULED);
  }

  // Create the report request frame if required
  if(fReportFrame == null) {
    fReportFrame = document.createElement("IFRAME");
    
    fReportFrame.style.width = "0px";
    fReportFrame.style.height = "0px";
    fReportFrame.style.display = "none";
  
    // Build URI with random value to prevent caching
    fReportFrame.src = PINIMAGES_PATH + "ts.gif";
    
    // Attach frame to body tag
    fBody.appendChild(fReportFrame);
  }
  
  // Build URI with random value to prevent caching
  fReportFrame.src = URI.addUrlParam("reportMode", "yes").addUrlParam("rnd", (new Date).valueOf());
  
  return false;
}

function cLoad() {
  if(!fStopLoad) log.setElementAttribute(window, "onunload", onUnload, false);

var menuId = Cx.doc.getElementById("M");
  
  // Remove event handler
  Cx.doc.body.onload = null;
  
  // Set the menu - if there is one
  if(Cx.lib.setMenu) Cx.lib.setMenu(menuId ? menuId.value : 0);
  
  // Set the enabled state of the add to favourites option
  // This is a really bad implementation, replace it with the 
  // replacement menu object...
  if(Cx.lib.setFavouriteAddEnabled && Cx.doc.getElementById("G") != null) {
    Cx.lib.setFavouriteAddEnabled(Cx.doc.getElementById("G").value);
  }

  if(fStopLoad) return;

  //Set instance variables
  fBody = Cx.doc.body;
  fHTML = Cx.doc.getElementsByTagName("HTML").item(0);


  // If this is running in the main library context, disable all scroll bars
  if(Cx == Cx.lib.Cx) {
    fIE ? fBody.scroll = "no" : fHTML.style.overflow = "-moz-scrollbars-none";
  }
  
  // Otherwise, force scroll bars on
  else {
    fIE ? fBody.scroll = "yes" : fHTML.style.overflowY = "scroll";
  }

  // Force IE to cache background images from CSS files
  if(fIE) Cx.doc.execCommand("BackgroundImageCache", false, true);
  
  //Do DOM parsing
  if(ENABLE_HOTKEYS)          doHotKeyHooking();      //Enable hot keys
  if(!DISABLE_SUBMIT_CONTROL) enableSubmitControl();  //Prevent double submit
  if(!DISABLE_POPUP_CONTROL ) enablePopupControl();   //Create all popups and date combos
  if(ENABLE_DESKTOP_ALERTS)   enableDesktopAlerts();  //Create the desktop alert object
  
  //Setup rollover images
  fRollovers = new Cx.lib.Rollovers("myRollover", Cx);
  
  //Call customisable function
  if(fWin.pLoad != undefined) fWin.pLoad();
  if(fWin.jv_messages != undefined) alert(jv_messages);

  // Refresh Rollovers
  fApplication.queueMessage(MSG_MAIN_REFRESH_ROLLOVERS, undefined);

  //Hook window events
  log.setElementAttribute(fBody,  "onclick",  doCloseObjects, false);
  log.setElementAttribute(window, "onresize", doWindowResize, false);
  log.setElementAttribute(window, "onscroll", doCloseObjects, false);

  container = fBody;
  
  fApplication._startThread();
//  if(_developer) return;

  // Need to optimise this section
  if (fDoc.getElementById("focusTabField") != null) {
    eval("selectTab('" + fDoc.getElementById("focusTabField").value + "');");
  }

  if (fDoc.getElementById("focusField") != null && fDoc.getElementById("focusField").value != "") {
    if (fDoc.getElementById("focusFieldType").value == "radio-set") {
      eval("try { fDoc.getElementById('mainForm')." + fDoc.getElementById("focusField").value + "[0].focus(); } catch(e) { }");
    }
    else {
      if(Cx.doc.getElementById("fi_showsearch") == null || Cx.doc.getElementById("fi_showsearch").value != "no") {
        eval("try { fDoc.getElementById('mainForm')." + fDoc.getElementById("focusField").value + ".focus(); } catch(e) { }");

        if (fDoc.getElementById("focusFieldType") != null) {
          if (fDoc.getElementById("focusFieldType").value == "fill-in") {
            eval("try { fDoc.getElementById('mainForm')." + fDoc.getElementById("focusField").value + ".select(); } catch(e) { }");
          }
        }
      }
    }
  }

  try { 
    if(fDoc.mainForm && fDoc.mainForm.rundates) {
      fDoc.mainForm.rundates.focus();
    } 
  }
  catch(e) { }

  
  // Update the page title if possible
  var newTitle = "";
  try {
    var tags = document.getElementsByTagName("TD");
    for(var i=0;i<tags.length;i++) {
      if(tags.item(i).className=="formtitle") {
        var ttl = tags.item(i).innerHTML.split(">")[1].split("<")[0].replace(/\n/, " ").replace(/\&nbsp;/g, " ").replace(/ /g, "¬").replace(/\s*/g, "").replace(/\¬/g, " ").replace(/  /g, " ").replace(/^\s*/g, "");
        if(ttl != "" && ttl.substr(0, 7) != "Showing") {
          newTitle = ttl;
          break;
        }
      }
    }
  }
  catch(e) {}
  
  // Set the title
  Cx.lib.setTitle(newTitle);

  // Call the load event of the bespoke js file - if there is one
  if(window.bespokeLoad) window.bespokeLoad();
}

/*--------------------------------------------------------
  Desktop Alert Implementation
  --------------------------------------------------------*/
var fAlertTmr = null;
var fAlertPollingFrequency = 0;
var fAlertCount = 0; // If this value is !0, window title notification is started
var fDa = null;

function enableDesktopAlerts() {
  var alertStruc = fDoc.getElementById("desktopAlert");
  var alertInterval = 3000;
  var alertQueue    = null;

  // If we're not running in the library context, point everything to the 
  // library instance
  if(Cx.lib != Cx.win) {
    fDesktopAlert = Cx.lib.fDesktopAlert;
    Cx.lib.fAlertCount = 0; // Reset count on new page
    
    // Get any alerts delivered by this page
    if(alertStruc) {
    
      // Extract the alert data
      alertQueue    = getDCOData(alertStruc.innerHTML, undefined, Cx, "");
      
      // Queue desktop alert if there is one
      if(alertQueue.data.length != 0) {

        // Remove key values generated in getDCOData call
        for(i=0;i<alertQueue.data.length;i++) alertQueue.data[i] = alertQueue.data[i].slice(3);

        // Queue the alerts
        Cx.lib.fApplication.queueMessage(MSG_MAIN_DESKTOP_ALERT, alertQueue.data);
      }
    }
    return;
  }
  
  // Create desktop alert object
  fDesktopAlert = new Cx.lib.DesktopAlertController("myAlerts", "BR", Cx);
  fDesktopAlert.maxOpacity    = 90;
  fDesktopAlert.onAlert       = doAlertEvent;
  fDesktopAlert.onAnchorClick = doAlertAnchorClick;
    
  // Only process if alert structure is found
  if(alertStruc) {
  
    // Get alert interval and alertQueue from alert structure
    alertInterval = parseInt(alertStruc.getAttribute("pcwPollingFreq"), 10) * 1000;
    alertQueue    = getDCOData(alertStruc.innerHTML, undefined, Cx, "");
  
    //Create desktop alert DCO
    fDa = new Cx.lib.DCO("Desktop Alert DCO", Cx);
    fDa.setFilter(Cx.lib.DCOFilterGrapevine);
    fDa.setBlockSize(10);
    fDa.onDataReady = daDataReady;
    fDa.onError     = daError;

    // Set desktop alert interval  
    if(alertInterval != 0) window.setInterval(doGetDesktopAlerts, alertInterval);

    // Queue desktop alert if there is one
    if(alertQueue.data.length != 0) {
      
      // Remove key values generated in getDCOData call
      for(i=0;i<alertQueue.data.length;i++) alertQueue.data[i] = alertQueue.data[i].slice(3);
      
      // Queue the alerts
      fApplication.queueMessage(MSG_MAIN_DESKTOP_ALERT, alertQueue.data);
    }
  }
}
  
function daError(pDCO, pErrorCode, pErrorMsg) { 
  return; //Ignore all errors
} 

function daDataReady(pDCO) {
  var data = new Array;
  var dataRow = new Array;

  pDCO.gotoFirst();

  while(!pDCO.EOF) {
    dataRow.length = 0;
    for(var i=0;i<pDCO.columnCount;i++) {
      dataRow.push(pDCO.getField(i));
    }

    data.push(dataRow.slice(0));
    pDCO.gotoNext();
  }

  // Queue the alert(s)
  fApplication.queueMessage(MSG_MAIN_DESKTOP_ALERT, data);
}

// Called every alertInterval milliseconds  
function doGetDesktopAlerts() {
  fDa.close();
  fDa.open(addSessionInfo("mpindesale10.htm"));
  fDa.getData(0, 10);
//  fDa.execute(addSessionInfo("mpindesale10.htm"));
}

// Called on every DesktopAlert.show() method call  
function doAlertEvent(pAlertStruc) {
  if(pAlertStruc[0] == DESKTOPALERT_TYPE_EMAIL) {
    Cx.lib.setMailIconVisible(true);
    Cx.doc.title = "(" + ++fAlertCount + ") New Message" + (fAlertCount != 1 ? "s" : "");
  }
}

function doAlertAnchorClick(pDesktopAlert) {
//  switch(pDesktopAlert.alertType) {
//    case DESKTOPALERT_TYPE_DOWNLOAD
//  }
  if(pDesktopAlert.alertUrl.indexOf("#") == -1) {
    _openUrl(pDesktopAlert.alertUrl);
    pDesktopAlert.hide();
  }
//  confirm("you clicked on a desktop alert link of type: " + pDesktopAlert.alertType + ", pointing to: " + pDesktopAlert.alertUrl);
}

function doDesktopAlert(pMsgData) {
  var i=0;
  
  doAlert();
  
  function doAlert() {
    if(pMsgData[i]) {
      fDesktopAlert.show(pMsgData[i++], Cx);
      window.setTimeout(doAlert, 1000);
    }
    
    function doAlert2() {
      fDesktopAlert.show(pMsgData[i++], Cx);
      window.setTimeout(doAlert, 1000);
    }
  }
}

/*-----------------------------------------------------
  Hotkey Implementation
  -----------------------------------------------------*/
function doHotKeyHooking() {
  if(Cx.lib.HotKeys) {
    Cx.win.hotKeys = new Cx.lib.HotKeys("Main Hotkeys", Cx);
    Cx.win.hotKeys.addListener(Cx.win, doHotKey);
    Cx.win.hotKeys.onKeyDown = doHotKeysKeyDown;
    Cx.win.hotKeys.onKeyUp   = doHotKeysKeyUp;
  }
}

function doHotKeysKeyDown(pHotKeys, pKeyCode) {
  Cx.lib.shiftKey = pHotKeys.shiftKey;
  Cx.lib.ctrlKey  = pHotKeys.ctrlKey;
}

function doHotKeysKeyUp(pHotKeys, pKeyCode) {
  Cx.lib.shiftKey = pHotKeys.shiftKey;
  Cx.lib.ctrlKey  = pHotKeys.ctrlKey;
}

//  function doHotKey(pSender, pKeyCode, pElement) {
function doHotKey(pSender, pKeyCode) {
  var nodeX;
  var nodeY;
  var obj;
  var eventHandled = true;
  var dataGrid;

  // Get the datagrid - currently we only support automation of the first datagrid
  // on the page, the second is up to you!
  dataGrid = _globalDataGridList[0];

  if(fWin.ENABLE_HOTKEY_NAVIGATION) {

    // Check for special cases where key should be ignored
    if(pSender.srcElement.tagName == "INPUT") {

      // Unless escape, csr_down or csr_up don't process anything
      switch(pKeyCode) {
        case Cx.lib.HOTKEY_ESCAPE:
        case Cx.lib.HOTKEY_CURSOR_DOWN:
        case Cx.lib.HOTKEY_CURSOR_UP:
          break;

        default:
          return false;
      }
    }

    if(pSender.srcElement.tagName == "SELECT")   return false;
    if(pSender.srcElement.tagName == "TEXTAREA") return false;

    switch (pKeyCode) {
      case Cx.lib.HOTKEY_ESCAPE:
        window.popupWindow ? window.parent.closeAllPopups() : window.closeAllPopups();
        break;

      case Cx.lib.HOTKEY_CURSOR_UP:

        if(!Cx.popupActive && ROW_HIGHLIGHT) {
          // If grid doesn't has focus, and on first row, remove focus
          // And put it back on the control it came from
          if(dataGrid && dataGrid.getHasFocus() && dataGrid.getFocusRowIndex() == 0) {
            dataGrid.blur();

            // Force any current control into focus (ignoring any error)
            try {if(fLastFocusedObject) fLastFocusedObject.focus()} catch(e) {}
          }
        }
        break;

      case Cx.lib.HOTKEY_CURSOR_DOWN:
        if(!Cx.popupActive && ROW_HIGHLIGHT) {
          // If grid doesn't have focus, tell it to focus
          if(dataGrid && dataGrid.getHasFocus() == false) {
            dataGrid.focus();

            // Force any current control out of focus (ignoring any error)
            try {if(fLastFocusedObject) fLastFocusedObject.blur()} catch(e) {}
          }
        }
        break;
        
      case Cx.lib.HOTKEY_CURSOR_RIGHT:
        if(_developer) {
          var temp = Cx.name + ":\n";
          for(var i=0;i<Cx.objectRegistry.length;i++) {
            temp += Cx.objectRegistry[i].constructor.toString().split("(")[0].split(" ")[1] + "\n";
          }
          confirm(temp);
          break;
        }

      default:
        eventHandled = false;
    }
  }

  return window.onHotKey(pSender, pKeyCode) || eventHandled;
}

function onUnload(e) {
//  confirm("freeing context: " + Cx.name);
  //closeAllPopups();
  //Call customisable function
  if(Cx.win.onBeforeUnload != undefined) Cx.win.onBeforeUnload();
  
  fApplication._stopThread();
  
  // Free all child contexts first
//  if(Cx.childContexts.length != 0) {
//    for(var i=Cx.childContexts.length-1;i>=0;i--) {
//      Cx.childContexts[i].win.onUnload();
//      Cx.childContexts[i] = null;
//    }
//  }
  
  // Clear any desktop alerts for this page's scope
  if(fDesktopAlert) fDesktopAlert.hideAlertsByScope(Cx);
//if(_developer) return;
  
  // This is a pointer to the library in non library context
  fDesktopAlert = null;
  
  Cx.doc.body.onload = null;
  
  fLastFocusedObject = null;
  fApplication._main = null;
  fApplication = null;
  
  // Free all DOM links from popup list
  for(var i=0;i<_globalCommonPopupList.length;i++) {
    _globalCommonPopupList[i].boundPopup = null;
    _globalCommonPopupList[i] = null;
  }
  _globalCommonPopupList = null;

  // Free all DOM links from popup INDEX list
  for(var i=0;i<_globalCommonPopupIndex.length;i++) {
    _globalCommonPopupIndex[i] = null;
  }
  _globalCommonPopupIndex = null;

  //Call customisable function
  if(Cx.win.pUnload != undefined) Cx.win.pUnload();
  
  if(Cx.win.popupDestroy) Cx.win.popupDestroy();
  
  Cx.free();
  Cx = null;
  
  //log.free(); // this is not required as it is freed automatically by the context
}

/* ------------------------------------------------------------------------------
   Popup control
   ------------------------------------------------------------------------------ */
function enablePopupControl(pContainer) {
  
  if(pContainer == undefined) pContainer = fDoc;

  // Bind fields and spans to popups based on pcwPopup attribute
  bindPopupsByCollection(pContainer.getElementsByTagName("INPUT"));
  bindPopupsByCollection(pContainer.getElementsByTagName("SPAN"));
  bindPopupsByCollection(pContainer.getElementsByTagName("DIV"));
  bindPopupsByCollection(pContainer.getElementsByTagName("TD"));
  bindPopupsByCollection(pContainer.getElementsByTagName("A"));
  bindPopupsByCollection(pContainer.getElementsByTagName("BUTTON"));

  // If in a popup window, call the redraw method
  if(pContainer != fDoc && (fThis.popupWindow || fThis.tickList)) doRedraw();
  
  // Implementation
  function bindPopupsByCollection(pCollection) {
    var element, i, len;

    len = pCollection.length;
    
    // Loop through all elements in this collection
    for(i=0;i<len;i++) {
    
      element = pCollection.item(i);
      
      // Only process if pcwPopup is set as an expando property of the element
      if(element.getAttribute("pcwPopup")    != null) fApplication.queueMessage(MSG_MAIN_CREATE_POPUP, element);//createPopupLink(element);
      if(element.getAttribute("pcwDataType") != null) fApplication.queueMessage(MSG_MAIN_CREATE_DATECOMBO, element);//createDateCombo(element);
      if(element.getAttribute("pcwTreeView") != null) createInlineTreeView(element);
      if(element.getAttribute("pcwCalendar") != null) fApplication.queueMessage(MSG_MAIN_CREATE_CALENDAR, element);//createInlineCalendar(element);
      if(element.getAttribute("pcwMenuBar")  != null) fApplication.queueMessage(MSG_MAIN_CREATE_MENUBAR, element);//createMenuBar(element);
      if(element.getAttribute("pcwWaitBox")  != null) fApplication.queueMessage(MSG_MAIN_CREATE_WAITBOX, element);//createWaitBox(element);
      if(element.getAttribute("pcwDataGrid") != null) createDataGrid(element);
      
      // If INPUT tag, hook focus event too
      if(element.tagName == "INPUT" && element.type.toUpperCase() == "TEXT") {
        log.setElementAttribute(element, "onfocus", doInputFocus, false);
      }
    }
  }
}

function doInputFocus(e) {
  var ev = fIE ? event : e;
  var obj = fIE ? ev.srcElement : ev.target;
  var dataGrid = _globalDataGridList[0];
  
  fLastFocusedObject = obj;
  
  // Remove focus from datagrid
  if(dataGrid && dataGrid.hasFocus) dataGrid.blur();

//  setFocus(null);
}

function doWindowResize(e) {
  var newWidth;
  
  // Adjust all dynamic elements
  doCloseObjects(e);
  doCentrePopups(e);
  doRedrawFloaters(e);
  
  
  // Resize any datagrid
  if(_globalDataGridList.length != 0) {
    
    // Adjust DG width according to screen type
    // >> Remove screen type when more of HTML metrics are known <<
    switch(Cx.win.SCREEN_TYPE) {
      case 1 : newWidth = Cx.doc.body.clientWidth - 223 - 14 - 10 - 2; break; // Explorer screens
      default: newWidth = Cx.doc.body.clientWidth - 10 - 2;           break;
    }
    
    // Set the new DG width
    _globalDataGridList[0].setCoords(_globalDataGridList[0].windowCoords.X,
                                     _globalDataGridList[0].windowCoords.Y,
                                     newWidth,
                                     _globalDataGridList[0].windowCoords.H);
  }
/*  
  // Set the size of any full width components (as 100% width doesn't work correctly)
  var frm = Cx.doc.getElementById("mainForm")
  
  if(frm) {
    var divs = frm.getElementsByTagName("DIV");
//    debugger;
    if(divs.length != 0) {// && divs.item(0).style.width == "100%") {
      divs.item(0).style.width = Cx.doc.body.clientWidth;
      if(divs.item(0).offsetWidth != Cx.doc.body.clientWidth) {
        divs.item().style.width = Cx.doc.body.scrollWidth;
      }
    }
  }
*/  
}

function doCentrePopups(e) {
  for(var i=0;i<_globalCommonPopupList.length;i++) {

    if(!_globalCommonPopupList[i].floatImage && _globalCommonPopupList[i].popupType != "DATE") {
      _globalCommonPopupList[i].boundPopup.centre();
    } 
  }
}

function redrawFloaters(e) {
//  if(!window._globalPageLoaded) return;

  var pCollection = fDoc.getElementsByTagName("BUTTON");
  for(var i=0;i<pCollection.length;i++) {

    if(pCollection.item(i).floatImage) {
      eCoords = getElementCoords(pCollection.item(i).boundElement);
      pCollection.item(i).style.top  = eCoords.Y;
      pCollection.item(i).style.left = eCoords.X;
    } 
  }
}

function doRedrawFloaters(e) {
  fWin.setTimeout(redrawFloaters, fIE ? 50:1000);
}

function doCloseObjects(e) {
  // Menu is now in persistent section of the page
  for(var i=0;i<Cx.lib._globalMenuBarList.length;i++) {
    Cx.lib._globalMenuBarList[i].closeMenus();
    Cx.lib._globalMenuBarList[i].menuVisible = false; // No time to implement this properly
  }

  if(!fProcessingPopup) {
    for(var i=0;i<_globalCommonPopupList.length;i++) {
      if(_globalCommonPopupList[i].popupType=="DATE") {
        _globalCommonPopupList[i].boundPopup.hide();
      }
    }
  }
}

function closeAllPopups() {
  // Just shut everything - poof.
  for(var i=0;i<_globalCommonPopupList.length;i++) {
    _globalCommonPopupList[i].boundPopup.hide(); // true = hide immediately (no anim)
  }
}

function createWaitBox(pElement) {
  log.setElementAttribute(pElement, "onclick", doWaitBox, false);
}

function doWaitBox(e) {
  var ev = fIE ? event : e;
  var obj = fIE ? ev.srcElement : ev.target;
  var message = obj.getAttribute("pcwWaitBox");
  
  if(message == undefined || message == "") message = "Please wait...";
  
  fWin.setTimeout(doWaitBoxTick, WAITBOX_TIMEOUT);
  
  function doWaitBoxTick(e) {
    var HTML = "";
    var fDiv;

    fDiv = fDoc.createElement("DIV");
    fDiv.id = "busyDiv";
    fDiv.style.position = "absolute";
    fDiv.style.top = (fDoc.getElementsByTagName("body").item(0).clientHeight - 58) / 2;
    fDiv.style.left = (fDoc.getElementsByTagName("body").item(0).clientWidth - 234) /2;
    fDiv.style.width = 234;
    fDiv.style.height = 58;
    fDiv.style.zIndex = "99";
    fDiv.style.filter = "progid:DXImageTransform.Microsoft.dropshadow(OffX=5, OffY=5, Color='gray', Positive='true')";
    fDiv.className = "waitBox";
    HTML += '<table width="100%" border="0" cellpadding="2" cellspacing="0">';
    HTML += '<tr>';
    HTML += '<td>';
    HTML += '<font size="3" face="Arial, Helvetica, sans-serif"><strong>' + message + '</strong></font><br>';
    HTML += '<font size="2" face="Arial, Helvetica, sans-serif">This could take several seconds.</font>';
    HTML += '</td>';
    HTML += '<td><img src="' + PINIMAGES_PATH + 'ani_hourglass.gif" width="10" height="54"></td>';
    HTML += '</tr>';
    HTML += '</table>';
    fDiv.innerHTML = HTML;
    fDoc.getElementsByTagName("body").item(0).appendChild(fDiv);
  }
}

function createPopupLink(pElement, pNoOnClick) {
  var button, popupFrame, popupWindow, popupAttr, popupName;
  var eCoords;

  // Create an image to act as a button and set default parameters
  button    = pElement;
  popupName = pElement.getAttribute("pcwPopup").toUpperCase();

  // If language popup, only create link if there is more than one language
  if(popupName == "TRANSLATION" && pElement.value == "") return ERROR_OK;
  
  // Get attributes structure for the specified popup
  popupAttr = eval("Cx.lib.ATTR_" + popupName);
  if(popupAttr == undefined) {
    alert("Unable to find popup attributes for popup '" + popupName + "'");
    return ERROR_OK + 1; // Not OK
  }

  // If processing an href or button, don't add extra button for popup invocation
  if(pElement.tagName != "A" && pElement.tagName != "BUTTON") button = fDoc.createElement("BUTTON");

  // If tag is a button, use the type="button" attribute to prevent it firing when return is pressed
  if(!fIE) {
  
    // IE doesn't have this problem
    if(button.tagName == "BUTTON") {
      button.type = "BUTTON";
    }
  }

  // Set popup attributes
  button.popupType          = popupName;
  button.floatImage         = (popupAttr.floatImage != undefined);
  button.doNotPass          = (popupAttr.doNotPass != undefined);
  button.scrolling          = popupAttr.scrolling == undefined ? "no" : "yes";
  button.disabled           = pElement.disabled;
  
  // Only set the boundPopup value if it has not already been set
  if(button.getAttribute("boundPopup") == null || button.getAttribute("boundPopup") == "") button.boundPopup         = INVALID_HANDLE;
    
  if(!pElement.disabled) {
    button.style.cursor       = fIE ? "hand" : "pointer";
    button.title  = popupAttr.title;
    
    if(!pNoOnClick) {
      log.setElementAttribute(button,  "onclick"    ,  popupControlClick);
    }
  }

  if(pElement.tagName != "A" && pElement.tagName != "BUTTON") {
    button.style.width    = DEFAULT_ICON_WIDTH;
    button.style.height   = DEFAULT_ICON_HEIGHT;
    button.style.display  = popupAttr.hideButton ? "none" : "";
    button.className      = "popupimg";
    button.style.backgroundColor = Cx.lib.getCSSValue(".listformbox", "backgroundColor");//getCSSRuleByName(".listformbox").style.backgroundColor;
    button.src            = Cx.lib.PCWIMAGES_PATH + popupAttr.disabledIcon;
    
    if(!pElement.disabled) {
      button.src    = Cx.lib.PCWIMAGES_PATH + popupAttr.icon;
      log.setElementAttribute(button,  "onmouseover",  popupButtonMouseOver);
      log.setElementAttribute(button,  "onmouseout" ,  popupButtonMouseOut);
    }

    button.style.backgroundImage = "url(" + button.src + ")";
  }

  // Hook the on blur event, to fire off code description lookup if required
  if(pElement.type && pElement.type.toUpperCase() != "HIDDEN") {
    log.setElementAttribute(pElement, "onchange", doCodeLookup);
  }

  log.setElementAttribute(pElement, "boundButton", button);
  log.setElementAttribute(button,   "boundElement", pElement);

  // If a float image, make it floatable above everything, by making the parent the body tag
  if(button.floatImage) {
    eCoords = getElementCoords(pElement);
    button.style.top = eCoords.Y;
    button.style.left = eCoords.X;
    button.style.position = "absolute";
    button.style.visiblity = "visible";
    fBody.appendChild(button);
  }
  
  // Insert element into document after fields, but before spans
  else {
    switch (pElement.tagName) {
      case "INPUT":
        pElement.parentNode.insertBefore(button, pElement.nextSibling);
        break;

      case "SPAN":
        pElement.parentNode.insertBefore(button, pElement);
        break;

      case "DIV":
        pElement.parentNode.insertBefore(button, pElement);
        break;
    }
  }
  
  // Store this popup in the global popup index (used for firing popups programatically)
  _globalCommonPopupIndex.push(pElement);
  
  // Eveything went OK
  return ERROR_OK;
}

function firePopup(pElementId, pElement) {
  var i, obj;

  // Element passed in?  
  if(pElement != undefined) {
  
    // Find the popup in the pre-generated list
    for(i=0;i<_globalCommonPopupIndex.length;i++) {
      if(_globalCommonPopupIndex[i] == pElement) break;
    }

    // If not found, attempt runtime creation
    if(i == _globalCommonPopupIndex.length) {
    
      // Create the popup linkage    
      var error = createPopupLink(pElement, true);

      // This element is not a popup
      if(error != ERROR_OK) {
        alert("Unable to fire popup<br>- Element '" + pElement.tagName + "' is not a popup.");
        return false;
      }
    }
    
    // Assign the object variable
    obj = pElement;
  }
  
  // Element not passed in, so look for it in the id list
  else {
  
    // Find the popup in the pre-generated list
    for(i=0;i<_globalCommonPopupIndex.length;i++) {
      if(_globalCommonPopupIndex[i].id == pElementId) break;
    }

    // If not found, attempt runtime creation
    if(i == _globalCommonPopupIndex.length) {
      alert("Unable to fire popup<br>- Element '" + pElementId + "' is not a popup.");
      return false;
    }

    // Get object reference to the BUTTON - not the input!
    else {
      obj = _globalCommonPopupIndex[i].boundButton;
    }
  }
  
  // Fire the popup by calling popupControlClick
  popupControlClickEx(obj, "");
  
  return false;
}

function popupButtonMouseOver(e) {
  var ev = fIE ? event : e;
  var obj = fIE ? ev.srcElement : e.target;
  obj.style.backgroundImage = "url(" + obj.src.split(".")[0] + "-r." + obj.src.split(".")[1] + ")";
}

function popupButtonMouseOut(e) {
  var ev = fIE ? event : e;
  var obj = fIE ? ev.srcElement : e.target;
  
  obj.style.backgroundImage = "url(" + obj.src + ")";
}

function createPopupForButton(pButton) {
  var button, popupFrame, popupWindow, popupAttr, popupName;
  
  button = pButton;
  
  var pElement = button.boundElement;

  // Get attributes structure for the specified popup
  popupName = pElement.getAttribute("pcwPopup").toUpperCase();
  popupAttr = eval("Cx.lib.ATTR_" + popupName);

  _globalCommonPopupList.push(button);

  // Set the popup source
  var url = popupAttr.popupSrc;
  
  // Add in the proper path
  var path = window.location.pathname.split("/");
  path[path.length - 1] = url;
  url = path.join("/");
  button.popupSrc     = new Cx.lib.String(addSessionInfo(url)).addUrlParam("fid", _globalCommonPopupList.length - 1);

  // Create a javascript window for the frame
//  if(_developer) {
//  popupWindow = new Cx.lib.Window("MyPopup", popupAttr.title, 10, 10, popupAttr.width, popupAttr.height, true, Cx.lib.Cx);
//  }
//  else {
  popupWindow = new Cx.lib.Window("MyPopup", popupAttr.title, 10, 10, popupAttr.width, popupAttr.height, true, Cx);
//  }
  popupWindow.onShow  = doWindowShow;
  popupWindow.onHide  = doWindowHide;
  popupWindow.setOverflow(popupAttr.scrolling == "yes" ? "auto" : "hidden");
  popupWindow.centre();
  
  log.setElementAttribute(button, "boundPopup", popupWindow);
}

function doCodeLookup(e) {
  var ev = fIE ? event : e;
  var obj = fIE ? ev.srcElement : ev.target;
  var popupFrame = null;
  var x = ev.clientX;
  var y = ev.clientY;
  var fiId = "";
  var fURL = "";

  if(obj.boundButton.boundPopup == INVALID_HANDLE) createPopupForButton(obj.boundButton);
  
  // Build URL
  fiId = obj.value;
  fURL = obj.boundButton.popupSrc;
  fURL = fURL.addUrlParam("fi_id", fiId); // Append the id to the url
  fURL = fURL.addUrlParam("fi_descLookup", "true"); // Append code lookup mode to url
  
  // Set other window properties
  obj.boundButton.boundPopup.setUrl(onBuildObjectUrl(fURL, obj.id));
}

function createDateCombo(pElement) {
  var button, calendar, comboAttr, topOffset, leftOffset, element;

  if(pElement.getAttribute("pcwDataType").toUpperCase()=="DATE") {
    // Get attributes
    comboAttr = Cx.lib.ATTR_DATE;

    // Create an image to act as a button and set default parameters
    calendar   = new Cx.lib.PopupCalendar("MyCalendarCombo", true, Cx);

    // Create an image for the button
    button              = fDoc.createElement("IMG");
    button.className    = "popupimg";
//    button.style.backgroundColor = "";
    button.style.width  = 20;
    button.style.height = 20;
    button.boundElement = pElement;
    button.popupType    = "DATE";
    log.setElementAttribute(button, "boundPopup", calendar);

    if(!pElement.disabled) {
      button.ro           = "yes";
      button.style.cursor = fIE ? "hand" : "pointer";
      log.setElementAttribute(button, "onclick",  popupControlClick);
      button.src      = PCWIMAGES_PATH + comboAttr.icon;
      button.alt      = comboAttr.title;
      button.title    = comboAttr.title;
    }
    else {
      button.src      = comboAttr.disabledIcon;
    }

    // Apply date formatting
    pElement.maxLength  = DATE_FORMAT.length;
    log.setElementAttribute(pElement, "onkeydown", doDateKeyDown);
    log.setElementAttribute(pElement, "onblur", doDateDisplay);

    _globalCommonPopupList[_globalCommonPopupList.length] = button;

    // Insert button after field
    pElement.parentNode.insertBefore(button, pElement.nextSibling);
  }

  function doDateKeyDown(e) {
    var ev  = fIE ? event : e;
    var obj = fIE ? ev.srcElement : ev.target;

    if(ev.keyCode != 9) obj.pcwModified = true;
    
  }

  function doDateDisplay(e) {
    var ev  = fIE ? event : e;
    var obj = fIE ? ev.srcElement : ev.target;
    var newDate = "";

    if(obj.pcwModified) {
      obj.pcwModified = false;
      if(obj.value != "") newDate = obj.value.toDate();
      if(newDate != null) {
        obj.value = newDate;
        pElement.nextSibling.boundPopup.setDate(newDate);
        if(fWin.onObjectSelection != undefined) {
          onObjectSelection(pElement.nextSibling.boundPopup, pElement.id);
        }
      }
      else {
        alert(Cx.lib.ERROR_INVALID_DATE);
      }
    }
  }
}

function createInlineCalendar(pElement) {
  var ATTR = pElement.getAttribute("pcwCalendar");

  log.setElementAttribute(pElement, "pcwCalendar", new Cx.lib.Calendar(pElement.id, undefined, Cx));
  pElement.pcwCalendar.setParent(pElement);
  pElement.pcwCalendar.setDate(ATTR.toDate("DD/MM/YYYY"));
  pElement.pcwCalendar.onClick = doCalendarClick;
  pElement.pcwCalendar.show();

  // Call user defined event to populate any other popup fields required
  onCustomBeforeShowObject(pElement.pcwCalendar, pElement.pcwCalendar.name);

  if(fWin.onBeforeShowObject != undefined) {
    onBeforeShowObject(pElement.pcwCalendar, pElement.pcwCalendar.name);
  }

  function doCalendarClick(pCalendar) {
    // Call user defined event to populate any other popup fields required
    onCustomObjectSelection(pCalendar, pCalendar.name);

    if(fWin.onObjectSelection != undefined) {
      onObjectSelection(pCalendar, pCalendar.name);
    }
  }
}

function createInlineTreeView(pElement, pOptions) {
  var ATTR = pElement.getAttribute("pcwTreeView");
  
  ATTR = ATTR.split("|");

  if(ATTR.length < 2) {
    alert("pcwTreeView attribute syntax is incorrect. pcwTreeView='&lt;TreeName&gt;|&lt;Options&gt;'");
    return null;
  }
  pOptions = eval(ATTR[1]);

  log.setElementAttribute(pElement, "pcwTreeView", new Cx.lib.TreeView(ATTR[0], undefined, undefined, pOptions, null, Cx));
  pElement.pcwTreeView.onSelectedChange = doTreeViewClick;
  pElement.pcwTreeView.lineHeight = DEFAULT_ICON_HEIGHT + (parseInt(DEFAULT_ICON_HEIGHT * (2/16), 10));

  pElement.pcwTreeView.setParent(pElement);

  if(fWin.onBeforeShowObject != undefined) {
    onBeforeShowObject(pElement.pcwTreeView, pElement.id);
  }

  function doTreeViewClick(pTreeView, pTreeNode) {
    if(fWin.onObjectSelection != undefined) {
      onObjectSelection(pTreeView, pElement.id);
    }
  }
  
  return pElement.pcwTreeView;
}

function createDataGrid(pElement) {
  var ATTR            = pElement.getAttribute("pcwDataGrid");
  var options;
  var gridConstructor = window.USE_TREEGRID ? Cx.lib.TreeGrid : Cx.lib.DataGrid;
  var d, elementHeight, gridObject;
  
  // Correct the DataGrid options list
  options = DATAGRID_HIDE_BORDER | DATAGRID_SCROLL_X_AUTO | DATAGRID_SCROLL_X_ALLOW_HEIGHT_ADJUST;
  options |= (window.DATAGRID_OPTIONS ? window.DATAGRID_OPTIONS : 0)
  options |= (window.COLUMN_ORDER     ? 0 : DATAGRID_NO_COLUMN_DRAG) 
  options |= (DISABLE_COLUMN_DRAGGING ? DATAGRID_NO_COLUMN_DRAG : 0) 
  options |= ((window.NAV_STYLE && (window.NAV_STYLE & 2) == 2) ? DATAGRID_SCROLL_Y_AUTO : 0);
  options |= (window.USE_TREEGRID ? DATAGRID_FIXED_HEIGHT : 0);
  
  // Get the data from the HTML
  d = getDCOData(pElement.innerHTML, pElement, Cx, "DataGrid DCO (Filter)"); // This is a spoofed call until proper column ordering is implemented

  // Initialise sort column    
  d.sortColumn = d.sortColumn.length >= 2 ? d.sortColumn : "1A";

  // Get new control height
  elementHeight = (LOOKUP_ROWS * DEFAULT_LINE_HEIGHT) + 20;

  // If grid object is not fixed height, allow control to shrink with data
  if(!(options & DATAGRID_FIXED_HEIGHT)) {
    elementHeight = Math.max(0, Math.min(parseInt(d.recordCount, 10) * DEFAULT_LINE_HEIGHT + 20, elementHeight));
  }
  
  // Create the grid object
  gridObject = new gridConstructor(ATTR, 0, 0, pElement.offsetWidth, elementHeight, options | DATAGRID_RENDER_INLINE, Cx);
  gridObject.sortMethod               = d.sortMethod;
  
  gridObject.onNeedData               = doDataGridNeedData;
  gridObject.onStatusChange           = doDataGridStatusChange;
  gridObject.onOptionClick            = doDataGridOptionClick;
  gridObject.onPageChange             = doDataGridPageChange;
  gridObject.onColumnResizeComplete   = doDataGridColResizeComplete;
  gridObject.onColumnOrderChange      = doDataGridColOrderChange;

  gridObject.setLineHeight(DEFAULT_LINE_HEIGHT);  
  gridObject.setParent(pElement);
  
//  gridObject.setColumns(d.columns);
//  function doAddColumn(pName, pWidth, pDataType, pFixedWidth, pLastColumn, pNoSelect, pNoSort, pTickList, pData) {
  for(var i=0;i<d.columns.length;i++) {
    gridObject.addColumn(d.columns[i].name,
                         d.columns[i].width,
                         d.columns[i].dataType,
                         false,
                         false,
                         d.columns[i].noSelect,
                         d.columns[i].noSort,
                         d.columns[i].tickList,
                         d.columns[i].colIndex);
  }
  
  gridObject.syncColWidths();
  gridObject.setTotalResults(d.recordCount);

  // Set the sort column by converting the backend column number into
  // a Datagrid column number through the COLUMN_ORDER array
  if(window.COLUMN_ORDER) {
    var sortCol = parseInt("0" + d.sortColumn, 10) - 1;
    var cols = "," + window.COLUMN_ORDER + ",";
    var index = cols.split("," + sortCol + ",")[0].replace(/[^,]/g, "").length;
    
    gridObject.setSortColumnEx(index, d.sortColumn.replace(/[^AD]/ig, "") == "A");
  }


  if(window.COLUMN_WIDTHS == undefined || eval("0" + window.COLUMN_WIDTHS.replace(/,/g,"+")) == 0) {
    gridObject.fitColumns();
  }
  // Create data communication object
  var Dco       = new Cx.lib.DCO("DataGrid DCO", Cx);
  Dco.setBlockSize(LOOKUP_ROWS * 2);
  Dco.setFilter(Cx.lib.DCOFilterGrapevine);
  Dco.open(Cx.doc.getElementById("G").value);
  Dco.onDataReady = doDCODataReady;
  Dco.onError     = doDCOError;
  Dco.tag         = gridObject; // Look into this link and try to find a better way to avoid cyclic links and memory leaks
  gridObject.DCO  = Dco;        // Look into this link and try to find a better way to avoid cyclic links and memory leaks
  
  var navOptions = NAVIGATOR_RENDER_INLINE;
  if(window.NAV_STYLE && (window.NAV_STYLE & 1) != 1) navOptions |= NAVIGATOR_HIDE_BUTTONS;
  
  var Nav         = new Cx.lib.Navigator("My Navigator", null, null, null, null, navOptions, Cx);

  Nav.setBoundControl(gridObject);
  Nav.setParent(pElement);

  // Register control with the parent's grid attribute (this is depreciated)
  log.setElementAttribute(pElement, "pcwDataGrid", gridObject);
  
  // Register control with global object list
  _globalDataGridList.push(gridObject);
  
  gridObject.gotoIndex(d.startRow - 1);

  // Call user event to allow object property overrides
  if(fWin.onBeforeShowObject != undefined) {
    onBeforeShowObject(gridObject, gridObject.name);
  }
}

function doRefreshDataGrid(pDataGrid) {
  // Default the datagrid if not specified
  if(!pDataGrid) pDataGrid = _globalDataGridList[0];

  // If datagrid handle is still BS, don't do anything    
  if(!pDataGrid) return;

  var temp = pDataGrid.dataPointer;
  
  pDataGrid.setData([]);
  pDataGrid.gotoIndex(temp);
  
  // Force "n to n of n" to update
  doDataGridPageChange(pDataGrid);
}

function doDataGridColOrderChange(pDataGrid) {
  // DO NOT set the window.COLUMN_ORDER global in this function!
  // The paging code needs the data in the same order as the original 
  // query so it can maintain the columns orders when the datagrid
  // columns have been altered by the user
  
  saveDataGridState(pDataGrid);
}

function doDataGridColResizeComplete(pDataGrid) {
  saveDataGridState(pDataGrid);
}

function saveDataGridState(pDataGrid) {
  doDataGridNeedData(pDataGrid, 0, 0, null, null);
}

function doDataGridPageChange(pDataGrid) {

  // Update showing n to n of n
  var el = fDoc.getElementById(pDataGrid.name + "Results");

  if(el && pDataGrid.totalResults != -1) {

    // Update element innerHTML and make sure element is visible
    el.innerHTML = (pDataGrid.totalResults == 0 ? Cx.lib.DATASET_NO_RESULTS : Cx.lib.DATASET_SHOWING + " " + (pDataGrid.dataPointer + 1) + " " + Cx.lib.DATASET_TO + " " + Math.min(pDataGrid.dataPointer + pDataGrid.rowCount, pDataGrid.totalResults) + " " + Cx.lib.DATASET_OF + " " + pDataGrid.totalResults + (pDataGrid.moreData ? "+" : ""));
    el.style.display = "";
  }

  // Surface page change event for library users
  if(window.onDataGridPageChange) onDataGridPageChange(pDataGrid);
}

function DCOData(pColumns, pData, pRecordCount, pStartRow, pSortMethod, pSortColumn, pColumnArray) {
  this.columns      = pColumns;
  this.data         = pData;
  this.recordCount  = pRecordCount;
  this.startRow     = pStartRow;
  this.sortMethod   = pSortMethod;
  this.sortColumn   = pSortColumn;
  this.columnArray  = pColumnArray;
}

// Legacy support for popup windows. 
// Remove this and stop the legacy checkbox selection working
// (This will be changed so don't depend on this or the parameters)
function doDataGridOptionClick(pDataGrid, pRowIndex) {
  // DO NOT use this event for ANYTHING. 
  // It is temporary and is subject to change without notice
  if(fWin.onRowSelect) {
    fWin.onRowSelect(pRowIndex); // Used for popup windows - very temporary
    return true;
  }
  
  // If one touch select is NOT enabled, cancel the option click event
  return !fWin.ROW_ONE_TOUCH_SELECT;
}

function doDataGridStatusChange(pDataGrid, pStatus, pSlowFade) { 
  if(pDataGrid.navigator) pDataGrid.navigator.setStatus(pStatus, pSlowFade);
}

function doDataGridNeedData(pDataGrid, pRowFrom, pDataLines, pCallback, pErrorCallback) {
  var newColOrder   = [];
  var newSortColumn = -1;
  var newColWidths  = [];
  var colCount      = 0;
  var url = document.getElementById("G").value;
  var filterParams = null;
  
  // Add in the proper path
  var path = window.location.pathname.split("/");
  path[path.length - 1] = url;
  url = path.join("/");

  // Close any open query
  pDataGrid.DCO.close();
  
  // If the datagrid has had a url filter set, close and re open the DCO query
  if(pDataGrid.filter != null) {
    filterParams = pDataGrid.filter.split("&");
    url = url.addUrlParam("fi_directoryid", filterParams[0]);
    filterParams.shift(0);
    if(filterParams.length != 0) url += "&" + filterParams.join("&");
    
  }
  
  /* Build the column order, column size and sort column values
     Then pass them on the URL to set the back end values
     (This could be extended into a query tunnel paradigm, allowing the sending
     of query related instructions to the server process e.g. SET_SORT_COLUMN:1) */
  
  // Get the list of backend column indexes in the datagrid column order
  colCount = pDataGrid.getColumns().length; // Need to call a method to get columns due to treegrid - do not change to columnCount!
  for(var i=0;i<colCount;i++) newColOrder.push(pDataGrid.getColumnData(i));
  
  // Get correct sort column
  newSortColumn = pDataGrid.getSortColumnIndex();
  newSortColumn = (newSortColumn != -1) ? pDataGrid.getColumnData(newSortColumn) : -1;

  // Get the list of column widths in the datagrid column order
  newColWidths = pDataGrid.getColumnWidths();  
  
  // Add the value to the new URL
  url = new Cx.lib.String(url).addUrlParam("cCol"    , (Number(newSortColumn) + 1) + (pDataGrid.getSortColumnAscending() ? "A" : "D"));
  url = new Cx.lib.String(url).addUrlParam("cSelCol" , newColOrder.join());
  url = new Cx.lib.String(url).addUrlParam("cSize"   , newColWidths.join());


  // Open the query
  pDataGrid.DCO.open(url);
  
  // Get a chunk of information
  pDataGrid.DCO.getData(pRowFrom, pDataLines);

  // Tell DataGrid to wait for response  
  return DATAGRID_DATA_PENDING;
}

function doDCOError(pDCO, pErrorCode, pErrorMsg) {
  pDCO.tag.dataLoaded(false, pErrorCode, pErrorMsg);
}

function doDCODataReady(pDCO) {
  var data = new Array;
  var resultsGrid = pDCO.tag;
  var headings = null;

  if(resultsGrid.columnCount == 0) {
    var info     = pDCO.getQueryInfo();
    var dataType = pDCO.getDataTypeArray();
    headings     = pDCO.getColumnArray();
  
    // Add the columns to the datagrid
    for(var i=0;i<headings.length;i++) {
      resultsGrid.addColumn(headings[i], 100, dataType[i].substr(0, 3), false, false, false, false, false);
    }
    resultsGrid.syncColWidths();
  }
  
/*  // Reset column headings

  // This doesn't work for unknown reasons. Columns that are dragged then go wrong on page refresh...
  var test = "";
  if(headings == null) headings = pDCO.getColumnArray();
  for(var i=0;i<resultsGrid.columnCount;i++) {
    test += "\n" + headings[i] + "(" + i + ") -> " + resultsGrid.getColumnData(i);
  }
  confirm(test);*/
  // Set the total number of results
  resultsGrid.setTotalResults(pDCO.totalResults);
    
  // Build the new buffer from the DCO data
  pDCO.gotoFirst();

  // Loop through records building the DataGrid data array  
  var loopCount = Math.floor(pDCO.recordCount / 8);
  var loopExtra = pDCO.recordCount % 8;
  
  if(loopExtra) {
    for(var i=loopExtra;i!=0;i--) {
      data.push(pDCO.fieldArray);
      pDCO.gotoNext();
    }
  }

  if(loopCount) {
    do {
      data.push(pDCO.fieldArray); pDCO.gotoNext();
      data.push(pDCO.fieldArray); pDCO.gotoNext();
      data.push(pDCO.fieldArray); pDCO.gotoNext();
      data.push(pDCO.fieldArray); pDCO.gotoNext();
      data.push(pDCO.fieldArray); pDCO.gotoNext();
      data.push(pDCO.fieldArray); pDCO.gotoNext();
      data.push(pDCO.fieldArray); pDCO.gotoNext();
      data.push(pDCO.fieldArray); pDCO.gotoNext();
    } while(--loopCount);
  }

  // Insert the data into the datagrid control
  resultsGrid.insertData(data, pDCO.queryPosition, pDCO.queryPosition + pDCO.recordCount, pDCO.getKeyArray());
  
  // Tell DataGrid the data has loaded OK
  resultsGrid.dataLoaded(true);
}

function getDCOData(pData, pElement, pContext, pDCOFilterName) {

  var dataColumns = new Array;
  var dataRows    = new Array;
  var columns     = "";
  var build       = "";
  var tag         = "";
  var columnCount = 0;
  var cols        = new Array;
  var dt          = "CHA"; 
  var curTag      = null;
  var orderColumns = pDCOFilterName == "DataGrid DCO (Filter)"; // Hard coded to only order columns for datagrid request

  var recordCount = 250;
  var startRow    = 0;
  var sortMethod  = "";
  var sortColumn  = "";
  
  // Grab attributes from container div
  var div = pData.split(/DIV/i);
  if(div.length > 1) {
    div = div[1].substring(0, div[1].indexOf(">")); // Remove trailing crap
    div = div.split(" ");
    for(var i=div.length-1;i!=-1;i--) {
      switch(div[i].substring(0, 5)) {
        case "pcwRe":
        case "pcwre": recordCount = div[i].split('"')[1]; break;
        case "pcwSt":
        case "pcwst": startRow    = div[i].split('"')[1]; break;
        case "pcwSC":
        case "pcwsc": sortMethod  = div[i].split('"')[1]; break;
        case "pcwCC":
        case "pcwcc": sortColumn  = div[i].split('"')[1]; break;
      }
    }
  }
  
  // Do headings
  var THList      = pData.split(/<TH/i);
  var THWidth     = "";
  var THDataType  = "";
  var THTickList  = "";
  var THInnerHTML = "";
  var THTag       = "";
  var THAttrList  = "";
  var THNoSelect  = "";
  var THNoSort    = "";
  
  var i, j;
  
  if(pElement != undefined) {
    pContext.win.COLUMN_TITLES = "";
  }
  
  // Extract all headings
  for(j=1;j<THList.length;j++) {
    THTag       = THList[j].split(">"); // Remove trailing crap
    THAttrList  = THTag[0].split(" ");
    THInnerHTML = THTag.slice(1).join(">").split(/<\/TH>/i)[0];// + " (" + (j-1) + ")";

    THWidth     = 10;
    THDataType  = "CHA";
    
    THNoSelect  = "";
    THNoSort    = "";
    THTickList  = "";
      
    for(i=0;i<THAttrList.length;i++) {
      
      switch(THAttrList[i].substring(0, 4)) {
        case "WIDT":
        case "widt":
        case "Widt":
          THWidth = Number(THAttrList[i].split('"')[1].replace(/[^0-9]/g,""));
          break;

        case "PCWN":
        case "pcwN":
        case "pcwn":
          // No sort flag
          if(THAttrList[i].substring(0, 7).toUpperCase() == "PCWNOSO") {
            THNoSort = THAttrList[i].split('"')[1];
          }
          
          // No select flag
          else {
            THNoSelect = THAttrList[i].split('"')[1];
          }
          break;

        case "PCWD":
        case "pcwD":
        case "pcwd":
          THDataType = THAttrList[i].split('"')[1].toUpperCase();
          break;

        case "PCWT":
        case "pcwT":
        case "pcwt":
          THTickList = THAttrList[i].split('"')[1];
          break;
      }
    }

    // Store the column
//    cols.push([THInnerHTML, THWidth, 0, (THDataType == "" ? "CHA" : THDataType), 0, THNoSelect, THNoSort, THTickList, j-1]);
    cols.push({name    : THInnerHTML, 
               width   : THWidth, 
               dataType: (THDataType == "" ? "CHA" : THDataType),
               noSelect: THNoSelect == "true",
               noSort  : THNoSort == "true",
               tickList: THTickList == "true",
               colIndex: j-1});
    
    if(pElement != undefined) pContext.win.COLUMN_TITLES += ',' + THInnerHTML;
  }

  if(pElement != undefined && pContext.win.COLUMN_TITLES != "") pContext.win.COLUMN_TITLES = pContext.win.COLUMN_TITLES.substr(1);

  var dataOrder = [];
    
  // Not a datagrid request, so don't order any of the columns based on the globals!
  if(!orderColumns) {
  
    // Just build a data order list of 0,1,2,3,4,5....
    for(var i=0;i<cols.length;i++) dataOrder.push(i);
  }

  // A datagrid request, so re-order the columns
  else {
    // Build a column order list if one is not specified
    if(pContext.win.COLUMN_ORDER == undefined || pContext.win.COLUMN_ORDER == "") {
      var colOrder = [];
      for(var i=0;i<cols.length;i++) colOrder.push(i);
      pContext.win.COLUMN_ORDER = colOrder.join();
    }

    // Build a dataOrder list
    // Sort the column order
    var colSorted = pContext.win.COLUMN_ORDER ? pContext.win.COLUMN_ORDER.split(",").sort(asInteger) : [];
    var colOrder  = pContext.win.COLUMN_ORDER ? pContext.win.COLUMN_ORDER.split(",") : [];
    for(var i=0;i<colSorted.length;i++) {
      for(var y=0;y<colOrder.length;y++) {
        if(Number(colOrder[y]) == Number(colSorted[i])) {
          dataOrder.push(y);
        }
      }
    }
  }

  // Do data
  var TRList        = pData.split(/<TR/i);
  var TRListLength  = TRList.length;
  var r;
  
  for(r=2;r<TRListLength;r++) {
    var TDList        = TRList[r].split(/<TD/i);
    var TDInnerHTML   = "";
    var TDListLength  = TDList.length;
    var TRAttrList    = TRList[r].split(">")[0].split(" ");

    // Get TR parameters
    var TRPcwId     = "";
    var TRPcwValue  = "";
    var TRPcwData   = "";

    for(var t=0;t<TRAttrList.length;t++) {
      switch(TRAttrList[t].substr(0, 4).toUpperCase()) {
        case "PCWI": TRPcwId = TRAttrList[t].split('="')[1].split('"')[0];    break;
        case "PCWV": TRPcwValue = TRAttrList[t].split('="')[1].split('"')[0]; break;
        case "PCWD": TRPcwData = TRAttrList[t].split('="')[1].split('"')[0];  break;
      }
    }

    // Process TDs
    for(j=1;j<TDListLength;j++) {
      TDInnerHTML = TDList[j].split(/<\/TD/i)[0];
      TDInnerHTML = TDInnerHTML.substr(TDInnerHTML.indexOf(">") + 1);
      if(TDInnerHTML.replace(/<[^(IMG)][^>]*>(.*?)/gi, "") == "") TDInnerHTML = "&nbsp;";
      
      // Hook all popups for runtime generation
      TDInnerHTML = TDInnerHTML.replace(/ pcwpopup=/gi, ' onclick="firePopup(' + "''" + ', this); return false" pcwpopup=');

      dataColumns[dataOrder[j-1]] = TDInnerHTML; //.replace(/<br>/gi, "");
    }

    // Store datarow and clear data buffer
    dataRows.push([TRPcwId, TRPcwValue, TRPcwData].concat(dataColumns));
    dataColumns = new Array;
  }
  
  var columns = "";
  var newCols = [];
    
  // Not a datagrid request, so just output the columns
  if(!orderColumns) {
    newCols = cols.slice(0);
  }
  
  // Datagrid request, so re-order the columns
  else {
  
    // Extract a list of columns in column erm... order.
    try {

      var colOrder = pContext.win.COLUMN_ORDER ? pContext.win.COLUMN_ORDER.split(",") : "";
      var widthOverride = pContext.win.COLUMN_WIDTHS ? pContext.win.COLUMN_WIDTHS.split(",") : [];

      if(cols.length != 0) {
        for(var i=0;i<colOrder.length; i++) {
          var col = cols[parseInt(colOrder[i], 10)];
          if(col) newCols.push(col);
          if(widthOverride[i] != undefined) newCols[i].width = Math.max(Number(widthOverride[i]), 48);
        }
      }
    }
    catch(e) {
      // This try/catch is a bodge to get around the global column_order 
      // list not being appropriate for any query sent other than the main 
      // DataGrid one - THIS NEEDS CHANGING ASAP
    }
  }

  return new DCOData(newCols, dataRows, recordCount, startRow, sortMethod, sortColumn);//, newCols);
//  return new DCOData(columns, dataRows, recordCount, startRow, sortMethod, sortColumn);//, newCols);
}

function asInteger(p1, p2) {
  var num1 = Number(p1);
  var num2 = Number(p2);
  
  if(num1 == num2) return 0;
  if(num1 > num2)  return 1;
  return -1;
}

function createMenuBar(pElement) {
  var menuBar;

  pElement.innerHTML = "";
  menuBar = new Cx.lib.MenuBar(pElement, Cx);

  log.setElementAttribute(pElement, "pcwMenuBar", menuBar);
  _globalMenuBarList[_globalMenuBarList.length] = menuBar;

  if(fWin.onCreateMenuBar != undefined) {
    onCreateMenuBar(menuBar, menuBar.id);
  }

  if(fWin.onBeforeShowObject != undefined) {
    onBeforeShowObject(menuBar, menuBar.id);
  }
}

var fLastPopupButton = null;

function popupControlClick(e) {
  var ev  = Cx.getEvent(e);//fIE ? event : e;
  var obj = fIE ? ev.srcElement : ev.target;
  
  // Call the wrapped method
  popupControlClickEx(obj, ev.type);

  return Cx.lib.cancelDOMEvent(ev);  
/*  // Cancel event bubbling
  ev.cancelBubble = true;
  ev.returnValue = false;
  
  // Cancel even in moz    
  if(!fIE) {
    ev.preventDefault();
    ev.stopPropagation();
  }
  
  return false;*/
}

// Gets the coords of the element relative to the browser viewport
function getElementViewportCoords(pElement) {

  var elCoords = new Cx.lib.Coords(pElement.offsetLeft,
                            pElement.offsetTop,
                            pElement.offsetWidth,
                            pElement.offsetHeight);
  
  if(pElement.offsetParent.tagName != "BODY") {//fBody) {
    elCoords.X -= pElement.offsetParent.scrollLeft;
    elCoords.Y -= pElement.offsetParent.scrollTop;
    
    var parCoords = getElementViewportCoords(pElement.offsetParent);
    
    elCoords.X += parCoords.X;
    elCoords.Y += parCoords.Y;
  }
  
  
  return elCoords;
}

function popupControlClickEx(pObj, pEventType) {
  var obj = pObj;
  var popupFrame = null;
//  var elCoords = Cx.lib.getElementCoordsEx(obj, Cx);
  var elCoords = getElementViewportCoords(obj);
  var x = elCoords.X;
  var y = elCoords.Y;
  var wasVisible;
  var isCodeLookup = false;
  
  // On leave event means code lookup, so get appropriate objects and set modifier flag
  if(pEventType == "change") {
    isCodeLookup = true;
    obj = obj.boundButton;
  }

  // Ensure the event object is actually a popup object
  while (obj.boundPopup == undefined) obj = obj.parentNode;

  fLastPopupButton = obj;
  if(obj.popupType != "DATE" && obj.boundPopup == INVALID_HANDLE) createPopupForButton(obj);

  wasVisible = obj.boundPopup.visible;
  
  if(!fProcessingPopup) {
    fProcessingPopup = true;

    for(var i=0;i<_globalCommonPopupList.length;i++) {
      _globalCommonPopupList[i].boundPopup.hide();
    }
    waitForPopupClose();
  }

  function waitForPopupClose() {
    var popupActive = false;

    for(var i=0;i<_globalCommonPopupList.length;i++) {
      popupActive = popupActive || _globalCommonPopupList[i].boundPopup.visible;
    }

    if(popupActive) {
      fWin.setTimeout(waitForPopupClose, 50);
    }
    else {
      if(!wasVisible) {
        showWindow();
      }
      else {      
        fProcessingPopup = false;
      }
    }
  }

  function showWindow() {
    if(!obj.boundPopup.visible && !wasVisible) {
      if(obj.popupType != "DATE") {
        // Set window size overrides
        if(obj.boundElement.getAttribute("pcwWidth")  != null) obj.boundPopup.windowCoords.W = Number(obj.boundElement.getAttribute("pcwWidth"));
        if(obj.boundElement.getAttribute("pcwHeight") != null) obj.boundPopup.windowCoords.H = Number(obj.boundElement.getAttribute("pcwHeight"));

        // Set other window properties
        obj.boundPopup.invocationCoords = new Cx.lib.Coords(x, y, 16, 16);
        obj.boundPopup.centre();

        if(!isCodeLookup) {
          obj.boundPopup.show(false);
        }
        else {
          obj.boundPopup.show(true);
        }
      }
      else {
        var elc = getElementCoords(obj.boundElement);
        obj.boundPopup.X = elc.X;
        obj.boundPopup.Y = elc.Y + elc.H;
        obj.boundPopup.invocationCoords = new Cx.lib.Coords(x, obj.boundPopup.Y, 16, 16);

        if((obj.boundPopup.Y + obj.boundPopup.H) > fBody.clientHeight + fBody.scrollTop) {
          obj.boundPopup.Y = Math.max(fBody.scrollTop, elc.Y - obj.boundPopup.H);
          obj.boundPopup.invocationCoords.Y = elc.Y;
        }

        if((obj.boundPopup.X + obj.boundPopup.W) > fBody.clientWidth + fBody.scrollLeft) {
          obj.boundPopup.X = (fIE ? 20 : 16) + Math.max(fBody.scrollLeft, (elc.X + elc.W) - obj.boundPopup.W);
          obj.boundPopup.invocationCoords.X = obj.boundPopup.X;
        }

        obj.boundPopup.boundElement = obj.boundElement;
        obj.boundPopup.show();
      }
    }
    
    waitForPopupOpen();
  }

  function doBuildObjectUrl(pUrl, pBoundField) {
    var fiId = "";
    
    // Don't add fi_id value for translation popups
    if(obj.popupType != "TRANSLATION") {

      switch(obj.tagName) {
        case "A":
          if(obj.getAttribute("pcwId") != null) fiId = obj.getAttribute("pcwId");
          break;

        case "BUTTON":
          if(obj.getAttribute("pcwId") != null) {
            fiId = obj.getAttribute("pcwId");
            break;
          }

        default:
          fiId = pBoundField.value;
      }
    }

    // Append the id to the url
    if(obj.doNotPass != true) pUrl = pUrl.addUrlParam("fi_id", fiId);
    else if(obj.boundElement.getAttribute("pcwId") != null)  pUrl = pUrl.addUrlParam("fi_id",obj.boundElement.getAttribute("pcwId"));

    // Append code lookup mode to url
    if(isCodeLookup) pUrl = pUrl.addUrlParam("fi_descLookup", "true");
    
    return onBuildObjectUrl(pUrl, pBoundField.id);
  }

  function waitForPopupOpen() {
    var popupActive = false;

    if(!isCodeLookup && !obj.boundPopup.visible && !wasVisible) {
      fWin.setTimeout(waitForPopupOpen, 50);
    }
    else {
      fProcessingPopup = false;
      if(obj.popupType != "DATE") {
        obj.boundPopup.setUrl(doBuildObjectUrl(obj.popupSrc, obj.boundElement));
      }
    }
  }

  // Fire before show object event for new popup
  if(fWin.onBeforeShowObject != undefined) {
    onBeforeShowObject(obj, obj.id);
  }
}

function doWindowShow(pWindow) {
// You can't disable the menus in case a user opens a 
// popup in the content context then hits a link in the 
// library context! Leaves the menus disabled.
//  Cx.lib.disableMenus(); 
  Cx.popupActive = true;
}

function doWindowHide(pWindow) {
  Cx.lib.enableMenus();
  Cx.popupActive = false;
  if(fLastPopupButton != null) {
    try {
      if(fLastPopupButton.tagName.toUpperCase() != "A") {
        fLastPopupButton.focus();
      }
    }
    catch(e) {}; // And error isn't important
  }
}

function enableMenus() {
  var i;

  for(i=0;i<_globalMenuBarList.length;i++) {
    _globalMenuBarList[i].setEnabled(true);
  }
}

function disableMenus() {
  var i;

  for(i=0;i<_globalMenuBarList.length;i++) {
    _globalMenuBarList[i].setEnabled(false);
  }
}


var fConfirm = null;

function doConfirm(pText, pTitle, pButtonSet, pOtherButtonText) {
  if(fConfirm == null) {
    fConfirm = new Cx.lib.ConfirmBox("Main Confirm Box");
    fConfirm.onButtonClick = doConfirmBoxButtonClick;
  }

  // Correct parameters
  if(pTitle == undefined || pTitle == "")               pTitle     = "Confirmation";
  if(pButtonSet == undefined || pButtonSet.length == 0) pButtonSet = CONFIRM_BTN_OK + CONFIRM_BTN_CANCEL;
  
  // Show the confirm and return the confirm id (Note, blocking call is not possible)
  return fConfirm.show(pTitle, pText, pButtonSet, pOtherButtonText);
}

function doConfirmBoxButtonClick(pConfirmBox, pButton) {
//  confirm(pConfirmBox.id + " - " + pConfirmBox.resultButton);
  if(window.onObjectSelection) window.onObjectSelection(pConfirmBox, pConfirmBox.id);
}

/* ----------------------------------------------------------------
   Submit control
   ---------------------------------------------------------------- */
function enableSubmitControl() {
  var i, form;
  var formCollection = fDoc.getElementsByTagName("FORM");
  
  if(fIE) {
    for(i=0;i<formCollection.length;i++) {
      form = formCollection.item(i);
      log.setElementAttribute(form, "onsubmit", utilitySubmit);
    }
  }
  
  else {
    window.onsubmit = utilitySubmit;
  }
}

function utilitySubmit(e) {
  var ev = fIE ? event : e;
  var form = fIE ? ev.srcElement : ev.target;
  var allowSubmit = true; // Assume this submit will be allowed to succeed (no clob data)
  var tags;
  var total = 0;
  var frm = null;
  var clobPointer = 0;
//  if(Cx.popupActive || fProcessingPopup) return false;
  
  // Make sure we have a form object, if not get default form
  if(form.tagName != "FORM") {
    form = document.getElementsByName("mainForm").item(0);
  }
  
  // If form has already been submitted, just return (Unless shift is held, forcing resubmit)
  if((!form.pcwEnableMultiSubmit == "true") && (form._submitted == "yes" && (window.hotKeys.shiftKey == false))) return false; // Do not allow multiple submits
  
  form._submitted = "yes";
  
  var tags = form.getElementsByTagName("TEXTAREA");
  var total = 0;
  var frm  = null;

  // Get approx form size, not including non-clob form data
  for(var i=0;i<tags.length;i++) {
    if(tags.item(i).id.indexOf("clob-") != -1) {
      total = total + tags.item(i).value.length;
    }
  }
  
  // If clobs are bigger than single form size (minus 4000 bytes for form data)
  // Start doing the multiple submits...
  if(total > 28000) {
   
    function doSubmitClob() {
    
      // Finished posting clobs
      if(clobPointer == tags.length) {

        // Disable clob fields
        for(var i=0;i<tags.length;i++) {
          if(tags.item(i).id.indexOf("clob-") != -1) {
            tags[i].disabled = true;
          }
        }

        // Now disable submit control, and submit the form
        form.onsubmit = null; // Belt and braces approach, just in case browsers change the rules anytime soon...
        form.submit();
      }
      
      // Post this clob
      else {

        // Create pointers
        var clob = tags[clobPointer++].value;
        var clobBlocks = Math.floor(clob.length / 28000) + 1;
        var clobBlockPointer = 0;

        function doSendChunk() {
          if(clobBlockPointer < clobBlocks) {
          
            // This event is fired by the transport layer - it is REQUIRED.
            Cx.win.onSendChunkComplete = chunkComplete;

            // Create submit container
            if(frm) {
              frm.parentNode.removeChild(frm);
              frm = null;
            }
            frm = fDoc.createElement("IFRAME");
            fBody.appendChild(frm);
            frm.style.position = "absolute";
            frm.style.top = 1;
            frm.style.left = 1;
            frm.style.width = 1;
            frm.style.height = 1;
            var frmDoc = frm.contentWindow.document;
            
            var formUrl = addSessionInfo("mpcwdatahu10.htm");
            formUrl = formUrl.addUrlParam("fi_dataidlist", tags[clobPointer-1].nextSibling.value);
            formUrl = formUrl.addUrlParam("fi_datalinelist", clobBlockPointer + 1);
            formUrl = formUrl.addUrlParam("fi_fieldlist", "clob");
            
            var HTML = '<HTML><HEAD>';
            HTML += '<meta http-equiv="Content-Type" content="text/html;CHARSET=ISO8859-1">';
            HTML += '</HEAD>';
            HTML += '<BODY bgcolor="#ffffff">';
            HTML += '<FORM id="clobForm" action="' + formUrl + '" method="POST">';
            HTML += '  <TEXTAREA name="clob"></TEXTAREA>';
            HTML += '</FORM>';
            HTML += '</BODY></HTML>';

            frmDoc.write(HTML);
            
            frmDoc.getElementsByTagName("TEXTAREA").item(0).value = clob.substr((clobBlockPointer++ * 28000), 28000);
            frmDoc.getElementsByTagName("FORM").item(0).submit();
          }
          
          else {
            doSubmitClob(); // Start sending next clob
          }
          
          function chunkComplete(pDataIdList, pDataLineList, pFieldList, pError) {
            if(pError != "") {
              alert("There was a problem sending your data<br>(" + pError + ")");
              form._submitted = "no";
            }
            else {
              tags[clobPointer-1].nextSibling.value = pDataIdList;
              window.setTimeout(doSendChunk, 50); // Give the browser chance to redraw
            }
          }
        }

        // Do not move this function lest Moz will stop working#
        doSendChunk();
      }
    }
    
    // Moved below declaration to make moz happy - DO NO MOVE
    doSubmitClob(); 
    
    allowSubmit = false;
  }
  
  return allowSubmit;
}


function addSessionInfo(p_URL) {
  var ar_url;
  var buttonProgram;
  var buttonParams;
  var buttonUrl = p_URL;
  var firstParam;

  if(Cx.doc.mainForm && Cx.doc.mainForm.S != undefined) {
    // Build infrastructure friendly url
    p_URL        += "?";
    buttonProgram = p_URL.split("?")[0];
    buttonParams  = p_URL.split("?")[1];
    ar_url        = buttonParams.split("&");
    p_URL         = p_URL.toUpperCase();

    // Add standard infrastructure and showroom parameters
//    if (p_URL.indexOf('&A=') == -1 && p_URL.indexOf('?A=') == -1) {ar_url[ar_url.length] = 'A=' + document.mainForm.A.value};
    if (p_URL.indexOf('&S=') == -1 && p_URL.indexOf('?S=') == -1) {ar_url[ar_url.length] = 'S=' + Cx.doc.mainForm.S.value};
    if (p_URL.indexOf('&P=') == -1 && p_URL.indexOf('?P=') == -1) {ar_url[ar_url.length] = 'P=' + Cx.doc.mainForm.P.value};
    if (p_URL.indexOf('&M=') == -1 && p_URL.indexOf('?M=') == -1) {ar_url[ar_url.length] = 'M=' + Cx.doc.mainForm.M.value};
    if (p_URL.indexOf('&L=') == -1 && p_URL.indexOf('?L=') == -1) {ar_url[ar_url.length] = 'L=' + Cx.doc.mainForm.L.value};
  
    buttonUrl  = buttonProgram;
    firstParam = true;
    for(var i=0;i<ar_url.length;i++){
      if(ar_url[i]!='') {
        // Add parameter separator
        if(firstParam) {
          buttonUrl += "?"
          firstParam = false;
        }
        else {
          buttonUrl += "&"
        }

        // Add parameter nv pair
        buttonUrl += ar_url[i];
      }
    }
  }

  return buttonUrl;
}

function getThisPage(){
  return Cx.doc.mainForm.G.value.split("?")[0];
}

/* ----------------------------------------------------------------
   Was pcwsearch.js, moved here to allow js library detachment and
   to avoid unneccessary use of a context parameter (this library
   is automatically scoped to the page it is running on - in 
   English this means there are multiple copies of this library 
   running at the same time - one for each frame or program running
   - all other libraries are persistent and available from the
   "Cx.lib" pointer)
   
   Below, Cx is the current context. The context for this page!
   ---------------------------------------------------------------- */
function fn_searchinit() {
  Cx.doc.getElementById('hidefields').style.display  = "";

  Cx.doc.getElementById("fi_showsearch").value != 'yes' ? fn_hidesearch(true) : fn_showsearch(true);
}

function fn_switchsearch() { 
  Cx.doc.getElementById("fi_showsearch").value != 'yes' ? fn_showsearch() : fn_hidesearch();        
} 
 
function fn_showsearch(pImmediate) {
  if (Cx.win.v_filterstatus != ""){
    var searchButton = Cx.doc.getElementsByName("searchtoggle").item(0);
    if(searchButton) {
      searchButton.src = Cx.lib.PCWIMAGES_PATH + 'minimise' + Cx.win.v_filterstatus + '.gif';
      searchButton.title = "Hide search criteria"
    }
  } 
  Cx.doc.getElementById("fi_showsearch").value = "yes";        
  if(pImmediate) {
    //Cx.doc.getElementById('hidefields').style.height = Cx.doc.getElementById('hidefields').scrollHeight + "px";
    Cx.doc.getElementById('hidefields').scrollTop = 0;
    Cx.doc.getElementById('hidefields').style.height = "";
  }
  else {
    tweenSearchHeight(1, Cx.doc.getElementById('hidefields').scrollHeight);
  }
}         

function fn_hidesearch(pImmediate) { 
  Cx.doc.getElementById('hidefields').style.overflow = "hidden";
  if (Cx.win.v_filterstatus != ""){
    var searchButton = Cx.doc.getElementsByName("searchtoggle").item(0);
    if(searchButton) {
      searchButton.src = Cx.lib.PCWIMAGES_PATH + 'maximise' + Cx.win.v_filterstatus + '.gif';
      searchButton.title = "Show search criteria"
    }
  }
  Cx.doc.getElementById("fi_showsearch").value = "no";
  if(pImmediate) {
    Cx.doc.getElementById('hidefields').style.height = "1px";
  }
  else {
    tweenSearchHeight(Cx.doc.getElementById('hidefields').scrollHeight, 1);
  }
}       

var fSearchTweenTmr = null;
function tweenSearchHeight(pFromX, pToX) {
  Cx.win.clearTimeout(fSearchTweenTmr);

  var curXPos = pFromX;
  var dX      = Math.abs(pToX - curXPos);
  var count   = 0;
  var ANIM_TIME = Cx.lib.ANIM_STEPS * (Cx.lib.ANIM_SPEED);
  var startTime = new Date().valueOf();
  var maxVal = Math.max(pFromX, pToX);

  // Correct deltas' direction
  if(curXPos > pToX) dX = dX * -1;

  // Animate it
  anim();

  function anim() {

    // Calculate animation frame based on real time
    var curTime = (new Date().valueOf()) - startTime;
    var timeLinePercent = Math.min((curTime / ANIM_TIME), 1);

    count = Math.min(Cx.lib.ANIM_STEPS, Math.round(Cx.lib.ANIM_STEPS * timeLinePercent));

    var val = (pFromX + Math.floor((dX * Cx.lib.fAcceleration[count]) + 0.5));
    Cx.doc.getElementById('hidefields').style.height = val;
    Cx.doc.getElementById('hidefields').scrollTop = maxVal - val;
  
    if(count != Cx.lib.ANIM_STEPS) {
      fSearchTweenTmr = Cx.win.setTimeout(anim, Cx.lib.ANIM_SPEED);
    }

    // Correct pixel position
    else {
      Cx.doc.getElementById('hidefields').style.height = pToX;
      Cx.doc.getElementById('hidefields').scrollTop = maxVal - pToX;

      if(pToX != 1) {
        Cx.doc.getElementById('hidefields').style.height = "";
        Cx.doc.getElementById('hidefields').style.overflow = "";
      }
    }
  }
}

function showToLink(linkName) {
  if (typeof eval("Cx.doc.getElementById('mainForm').fi_showsearch") != "undefined")
    linkName.href = linkName.href + '&fi_showsearch=' + Cx.doc.getElementById("mainForm").fi_showsearch.value;
}

/* ----------------------------------------------------------------
   Overridable events
   ---------------------------------------------------------------- */
function onBuildObjectUrl(pUrl, pBoundFieldId) {
  return pUrl;
}

function onCustomBeforeShowObject(pObject, pId) {
}

/* ----------------------------------------------------------------
   Override libraries
   ---------------------------------------------------------------- */

// Override alerts
if(ENABLE_DESKTOP_ALERTS) {
  eval('function alert(pText) {'
   + '    var alerts = pText.split("\\n");'
   + '    for(var i=0;i<alerts.length;i++) {'
   + '      if(alerts[i] != "") fApplication.queueMessage(MSG_MAIN_DESKTOP_ALERT, new Array([DESKTOPALERT_TYPE_ERROR, "System Alert", alerts[i], "#"]));'
   + '    }'
   + '  }');
}

function info(pText) {
  alert(pText);
}

if(ENABLE_DESKTOP_ALERTS) {
  eval('function info(pText) {'
  + '     var alerts = pText.split("\\n");'
  + '     for(var i=0;i<alerts.length;i++) {'
  + '       if(alerts[i] != "") fApplication.queueMessage(MSG_MAIN_DESKTOP_ALERT, new Array([DESKTOPALERT_TYPE_INFO, "Information", alerts[i], "#"]));'
  + '     }'
  + '   }');
}

eval('function confirmMe(pText, pTitle, pButtonSet, pOtherButtonText) {'
+ '     doConfirm(pText, pTitle, pButtonSet, pOtherButtonText)'
+ '   }');

if(window.libraryLoaded) window.libraryLoaded("pcwcommon", 1.24);
