﻿/*
CS Javascript Framework - Enterprise Ajax Core
This core javascript library is developed for general use
All the functions here should be as generic as possible

Versioning Convention
---------------------
v.[major].[minor]
Major : Change in signature
Minor : Bug fixes and feature enhancement

TODO
----
Add code to check if the script had been included before

*/

var ex = {
    version: 1.0,

    /*************************
        DEBUGGING FUNCTIONS
    **************************/

    /*
    Name        : createXMLHttpRequest
    Description : A general function to create XMLHttpRequest object
    Revision    : 2007-11-20 v1.0 First Release
    */
    createXMLHttpRequest: function()
    {
        try {
            var xmlreq = new ActiveXObject("Msxml2.XMLHTTP");
            return xmlreq;
        } catch (e) {
            //alert(cs.Debug.inspect(e));   
        }
        
        try {
            var xmlreq = new ActiveXObject("Microsoft.XMLHTTP");
            return xmlreq;
        } catch (e) {
            //alert(cs.Debug.inspect(e));
        }
        
        try {
            var xmlreq = new XMLHttpRequest();
            return xmlreq;
        } catch(e) {
            if (window.debugmode==true)
                alert(cs.Debug.inspect(e));
        }
        
        cs.debugalert("XMLHttpRequest not supported");
        return null;
    }
    ,
    
    toJSON: function(obj) 
    { // Taken from http://devers.blogspot.com/2007/09/worlds-smallest-tojson-function.html
        switch (typeof obj) {
            case 'object':
                if (obj) {
                    var list = [];
                    if (obj instanceof Array) {
                        for (var i=0;i < obj.length;i++) {
                            list.push(this.toJSON(obj[i]));
                        }
                        return '[' + list.join(',') + ']';
                    } else {
                        for (var prop in obj) {
                            list.push('"' + prop + '":' + this.toJSON(obj[prop]));
                        }
                        return '{' + list.join(',') + '}';
                    }
                }
                else
                {
                    return 'null';
                }
            case 'string':
                return '"' + obj.replace(/(["'])/g, '\\$1') + '"';
            case 'number':
            case 'boolean':
                return new String(obj);
        }
        return null;
    },
    
    AJAXServerList : new Array(),
    
    AJAXServer : function(serverURL)
    {
        // A single instance of AJAX caller, which handles ajax calls
        this._ServerURL = serverURL;
        this._AjaxAuthCode = "ajax-csframework";
        this._SessionID = "invalidsession";
        
        this._prepareConnection = function()
        {
            if (this._XReq==null)
            {
                this._XReq = ex.createXMLHttpRequest();
            }
            else
            {
                if (this._XReq.readyState>0 && this._XReq.readyState<4)
                {
                    this._XReq = ex.createXMLHttpRequest();
                }
            }
        };
        
        this.setAjaxAuthCode = function(strAuthCode)
        {
            this._AjaxAuthCode = strAuthCode;
        };
        
        this.getAjaxAuthCode = function()
        {
            return this._AjaxAuthCode;
        };
        
        this.getSessionID = function()
        {
            return this._SessionID;
        };
        
        this.setSessionID = function(strSessionID)
        {
            this._SessionID = strSessionID;
        };
        
        this.CallServer = function(strMod, strCmd, jsonData, context)
        {
            this._prepareConnection();

            // Need to gather the following from the switch:
            // 1) Request Method: GET or POST
            var strRequestMethod = null;
            
            strMod = strMod.toLowerCase();
            strCmd = strCmd.toLowerCase();
            
            // Search the loaded modules
            if (window.global_servermodule==undefined)
            {
                // TODO: Interesting, the variable must be defined after the check
                window.global_servermodule = {};
                cs.debugalert("no server module registered to run.");
                return;
            }

            if (window.global_servermodule[strMod]!=undefined)
            {
                var mod_spec = window.global_servermodule[strMod][strCmd];
                if (mod_spec!=undefined)
                {
                    strRequestMethod = mod_spec.method;
                }
            }
            
            if (strRequestMethod==null)
            {
                cs.debugalert("invalid request, unknown module or command.\n(module:" + strMod + ", command:" + strCmd + ")\n\n" +
                        "postData:" + cs.Debug.inspect(jsonData).join(""));
                return;
            }
            
            this._XReq.open(strRequestMethod, this._ServerURL, true);
            
            this._XReq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            
            // The ajax authentication token to be put here
            this._XReq.setRequestHeader("ajax-auth", this._AjaxAuthCode);

            // The ajax action to be invoked
            this._XReq.setRequestHeader("module", strMod);
            this._XReq.setRequestHeader("command", strCmd);
            this._XReq.setRequestHeader("command-token", "someaction token here");
            this._XReq.setRequestHeader("sessionid", this.getSessionID());

            // Added by CS on 2008-05-26, to record the post data in the context also
            context.postData = jsonData;
            // Added by CS on 2008-07-30, to record the module and command for replay purpose
            context.postModule = strMod;
            context.postCommand = strCmd;
            
            var callInfo = {};
            callInfo._XReq = this._XReq;
            callInfo._context = context;
            callInfo._context.server = this;
            
            // Added by CS on 2008-05-26
            // Assign the customer result handler if it is supplied
            if (context.customresulthandler != undefined && context.customresulthandler.constructor == Function)
            {
                callInfo._context.module.handlers[200]["customresult"] = context.customresulthandler;
            }
            
            this._XReq.onreadystatechange = function()
            {
                
                if (callInfo._XReq.readyState != 4)  { return; }

                // For debugging purpose
                if (callInfo._context.showResponseData==true)
                    alert(cs.Debug.inspect(callInfo._XReq.responseText));
                if (callInfo._context.showPostData==true)
                    alert(cs.Debug.inspect(callInfo._context.postData));

                // Check the return status
                if (callInfo._XReq.status==200)
                {
                    //document.write("<pre>" + callInfo._XReq.responseText.replace(/\</gi, "&lt;") + "</pre>");
                    // Load the response text in XML
                    var xmlDocResponse = XML.parse(callInfo._XReq.responseText);
                    
                    //var eleResultPanel = document.getElementById("divResultPanel");
                    //eleResultPanel.appendChild(cs.Debug.inspectToHTML(XML.XMLNodetoJXML(xmlDocResponse), {depth:10}));
                    //var json = XML.XMLNodetoJSON(xmlDocResponse);
                    //alert(cs.Debug.inspect(json, {depth:6}).join(""));
                    if (callInfo._context.handler!=undefined)
                    {
                        if (callInfo._context.handler.constructor == Function)
                        {
                            var jsonResponse = XML.XMLNodetoJSON(xmlDocResponse);
                            callInfo._context.handler(callInfo._context, jsonResponse);
                        }
                        else
                        {
                            throw "Handler is not a function";
                        }
                    }
                    else
                    {
                        if (callInfo._context.module.handler.default_handler != undefined)
                        {
                            // TODO: Added in for debugging purpose
                            //callInfo._context.responseText = callInfo._XReq.responseText;
                            callInfo._context.module.handler.default_handler(callInfo._context, XML.XMLNodetoJSON(xmlDocResponse));
                        }
                        else
                            throw "Handler is not defined, and even default handler is not defined.";
                    }
                }
                else
                {

                    if (callInfo._XReq.status=="0" || callInfo._XReq.status=="12002")
                    {
                        alert("Request timeout. The server is not responding to your request at the moment. Please try again later.");
                    }
                    else
                    {
                        alert("The system is unable to process your request now, please try again later.\bStatus Code:" + callInfo._XReq.status);
                    }

                    // TODO: Need to create a friendly error here and call the handler to handle it.
                    //cs.debugalert("Error: Request Status Code is " + callInfo._XReq.status);
                    
                    // ADDED by CS 20090107, to have debugging capability by developer
                    if (window.debugmode==true)
                        window.document.write(callInfo._XReq.responseText);

                                        
                }
                
            };

            if (strRequestMethod=="POST")
            {
                // All the rest are put in the content for POST
                var jsonRequestPacket = {RequestModule:strMod, RequestAction:strCmd, RequestBody: jsonData};
                //cs.debugalert(cs.Debug.inspect(jsonRequestPacket).join(""));
                var xmlDoc = XML.JSONtoXMLDoc(jsonRequestPacket, "RequestPacket");
                
                // TODO: This method of getting the xml string is not very efficient.
                // Have to check if there's better ones.
                var xmlContent = null;
                if (window.ActiveXObject)
                    xmlContent = xmlDoc.xml;
                else
                {
                    xmlContent = (new XMLSerializer()).serializeToString(xmlDoc);
                }

                this._XReq.send(xmlContent);
            }
            else
            {
                this._XReq.send(null);
            }
            
        };

    }
    
}

// Taken From http://www.ajaxify.com/run/Lib/js/util.js
var util = {

  shouldDebug: false,

  // Note: Will fail in pathological cases (where the members contain
  // strings similar to describe() result).
  membersEqual: function(array1, array2) {
    return util.describe(array1)==util.describe(array2);
  },

  describe: function(obj) {
    if (obj==null) { return null; }
    switch(typeof(obj)) {
      case 'object': {
        var message = "";
        for (key in obj) {
          message += ", [" + key + "]: [" + obj[key] + "]";
        }
        if (message.length > 0) {
          message = message.substring(2); // chomp initial ', '
        }
        return message;
      }
      default: return "" + obj;
    }
  },

  debug: function(message) {
      if (this.shouldDebug) {
        cs.debugalert("AjaxJS Message:\n\n" + message);
      }
  },

  error: function(message) {
      if (this.shouldDebug) {
        cs.debugalert("AjaxJS ERROR:\n\n" + message);
      }
  },

  trim: function(str) {
    return str.replace(/(^\s+|\s+$)/g,'');
  },

  strip: function(str) {
    return str.replace(/\s+/, "");
  }

}

function $() {

    var elements = new Array();

    for (var i = 0; i < arguments.length; i++) {

      var element = arguments[i];

      if (typeof element == 'string') {
        if (document.getElementById) {
          element = document.getElementById(element);
        } else if (document.all) {
          element = document.all[element];
        }
      }

      elements.push(element);

    }

    if (arguments.length == 1 && elements.length > 0) {
      return elements[0];
    } else {
      return elements;
    }
}

function $C(elType) {
  return document.createElement(elType);
}

// From prototype library. Try.these(f1, f2, f3);
var Try = {
  these: function() {
    var returnValue;
    for (var i = 0; i<arguments.length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) {}
    }
    return returnValue;
  }
}

function getElementsByClassName(classname) {
    var a = [];
    var re = new RegExp('\\b' + classname + '\\b');
    var els = document.getElementsByTagName("*");
    for(var i=0,j=els.length; i<j; i++)
        if(re.test(els[i].className))a.push(els[i]);
    return a;
}

function extractIFrameBody(iFrameEl) {

  var doc = null;
  if (iFrameEl.contentDocument) { // For NS6
    doc = iFrameEl.contentDocument; 
  } else if (iFrameEl.contentWindow) { // For IE5.5 and IE6
    doc = iFrameEl.contentWindow.document;
  } else if (iFrameEl.document) { // For IE5
    doc = iFrameEl.document;
  } else {
    cs.debugalert("Error: could not find sumiFrame document");
    return null;
  }
  return doc.body;

}

// Taken From http://www.ajaxify.com/run/Lib/js/ajaxCaller.js
// ***************************
var ajaxCaller = {

  shouldDebug: false,
  shouldEscapeVars: false,
  shouldMakeHeaderMap: true,

  calls : new Array(),
  pendingResponseCount : 0,

   /**************************************************************************
      PUBLIC METHODS
   *************************************************************************/

  getXML: function(url, callbackFunction) {
    this.get(url, null, callbackFunction, true, null);
  },

  getPlainText: function(url, callbackFunction) {
    this.get(url, null, callbackFunction, false, null);
  },

  postForPlainText: function(url, vars, callbackFunction) {
    this.postVars(url, vars, null, callbackFunction, false,
                    null, "POST", null, null, null);
  },

  postForXML: function(url, vars, callbackFunction) {
    this.postVars(url, vars, null, callbackFunction, true,
                    null, "POST", null, null, null);
  },

  get: function(url, urlVars, callbackFunction, expectingXML, callingContext) {
    this._callServer(url, urlVars, callbackFunction, expectingXML,
                    callingContext, "GET", null, null, null);
  },

  postVars:
    function(url, bodyVars, optionalURLVars, callbackFunction, expectingXML,
             callingContext) {
      this._callServer(url, optionalURLVars, callbackFunction, expectingXML,
                      callingContext, "POST", bodyVars, null, null);
  },

  postBody:
    function(url, optionalURLVars, callbackFunction, expectingXML,
             callingContext, bodyType, body) {
      this._callServer(url, optionalURLVars, callbackFunction, expectingXML,
                      callingContext, "POST", null, bodyType, body);
  },

  putBody:
    function(url, optionalURLVars, callbackFunction, expectingXML,
             callingContext, bodyType, body) {
      this._callServer(url, optionalURLVars, callbackFunction, expectingXML,
                      callingContext, "PUT", null, bodyType, body);
  },

  options:
    function(url, optionalURLVars, callbackFunction, expectingXML,
             callingContext, bodyType, body) {
      this._callServer(url, optionalURLVars, callbackFunction, expectingXML,
                      callingContext, "OPTIONS", null, bodyType, body);
  },

  trace:
    function(url, optionalURLVars, callbackFunction, expectingXML,
             callingContext, bodyType, body) {
      this._debug("trace");
      this._callServer(url, optionalURLVars, callbackFunction, expectingXML,
                      callingContext, "TRACE", null, bodyType, body);
  },

  deleteIt: function(url, urlVars, callbackFunction,
                     expectingXML, callingContext) {
    this._callServer(url, urlVars, callbackFunction, expectingXML,
                    callingContext, "DELETE", null, null, null);
  },

  head: function(url, urlVars, callbackFunction, expectingXML, callingContext)
  {
    this._callServer(url, urlVars, callbackFunction, expectingXML,
                    callingContext, "HEAD", null, null, null);
  },

  /**************************************************************************
     PRIVATE METHODS
  *************************************************************************/

  _callServer: function(url, urlVars, callbackFunction, expectingXML,
                       callingContext, requestMethod, bodyVars,
                       explicitBodyType, explicitBody) {

    if (urlVars==null) {
      urlVars = new Array();
    }

    this._debug("_callServer() called. About to request URL\n"
                + "call key: [" + this.calls.length + "]\n"
                + "url: [" + url + "]\n"
                + "callback function: [" + callbackFunction + "]\n"
                + "treat response as xml?: [" + expectingXML + "]\n"
                + "Request method?: [" + requestMethod + "]\n"
                + "calling context: [" + callingContext + "]\n"
                + "explicit body type: [" + explicitBodyType + "]\n"
                + "explicit body: [" + explicitBody + "]\n"
                + "urlVars: [" + util.describe(urlVars) + "]\n"
                + "bodyVars: [" + util.describe(bodyVars) + "]"
              );


    var xReq = this._createXMLHttpRequest();
    xReq.onreadystatechange = function() {
      ajaxCaller._onResponseStateChange(call);
    }

    var call = {xReq: xReq,
                callbackFunction: callbackFunction,
                expectingXML: expectingXML,
                callingContext: callingContext,
                url: url};

    if (urlVars!=null) {
      var urlVarsString = this._createHTTPVarSpec(urlVars);
      if (urlVarsString.length > 0) { // TODO check if appending with & instead
        url += "?" + urlVarsString;
      }
    }

    xReq.open(requestMethod, url, true);

    if (   requestMethod=="GET"
        || requestMethod=="HEAD"
        || requestMethod=="DELETE") {
      this._debug("Body-less request to URL " + url);
      xReq.send(null);
      return;
    }

    if (   requestMethod=="POST"
        || requestMethod=="PUT"
        || requestMethod=="OPTIONS"
        || requestMethod=="TRACE") {
      bodyType = null;
      body = null;
      if (explicitBodyType==null) { // It's a form
        bodyType = 'application/x-www-form-urlencoded; charset=UTF-8';
        body = this._createHTTPVarSpec(bodyVars);
      } else {
        bodyType = explicitBodyType;
        body = explicitBody;
      }
      this._debug("Content-Type: [" + bodyType + "]\nBody: [" + body + "].");
      xReq.setRequestHeader('Content-Type',  bodyType);
      xReq.send(body);
      return;
    }

    this._debug("ERROR: Unknown Request Method: " + requestMethod);


  },

  // The callback of xmlHttpRequest is a dynamically-generated function which
  // immediately calls this function.
  _onResponseStateChange: function(call) {

    xReq = call.xReq;

    if (xReq.readyState < 4) { //Still waiting
      return;
    }

    if (xReq.readyState == 4) { //Transmit to actual callback
      this._debug("Call " + util.describe(call)
                + " with context [" + call.callingContext+"]"
                + " to " + call.url + " has returned.");
      callbackFunction = call.callbackFunction;
      if (!callbackFunction) { // Maybe still loading, e.g. in another JS file
        setTimeout(function() {
          _onResponseStateChange(call);
        }, 100);
      }
      var content = call.expectingXML ? xReq.responseXML : xReq.responseText;
      responseHeaders = xReq.getAllResponseHeaders();
      headersForCaller = this.shouldMakeHeaderMap ?
        this._createHeaderMap(responseHeaders) : responseHeaders;
      callbackFunction(content, headersForCaller, call.callingContext);
    }

    call = null; // Technically the responsibility of GC
    this.pendingResponseCount--;

  },

  // Browser-agnostic factory function
  _createXMLHttpRequest: function() {
    if (window.XMLHttpRequest) {
      return new XMLHttpRequest();
    } else if (window.ActiveXObject) {
      return new ActiveXObject('Microsoft.XMLHTTP')
    } else {
      _error("Could not create XMLHttpRequest on this browser");
      return null;
    }
  },

  _createHTTPVarSpec: function(vars) {
      var varsString = "";
      for( key in vars ) {
        var value = vars[key];
        if (this.shouldEscapeVars) {
          escapePlusRE =  new RegExp("\\\+");
          value = value.replace(escapePlusRE, "%2B");
        }
        varsString += '&' + key + '=' + value;
      }
      if (varsString.length > 0) {
        varsString = varsString.substring(1); // chomp initial '&'
      }
      this._debug("Built var String: " + varsString)
      return varsString;
   },

  /* Creates associative array from header type to header */
  _createHeaderMap: function(headersText) {
    extractedHeaders = headersText.split("\n");
    delete extractedHeaders[extractedHeaders.length]; // Del blank line at end
    headerMap = new Array();
    for (i=0; i<extractedHeaders.length-2; i++) {
      head = extractedHeaders[i];
      fieldNameEnding = head.indexOf(":");
      field = head.substring(0, fieldNameEnding);
      value = head.substring(fieldNameEnding + 2, head.length);
      value = value.replace(/\s$/, "");
      headerMap[field] = value;
    }
    return headerMap;
  },

  _debug: function(message) {
      if (this.shouldDebug) {
        cs.debugalert("AjaxJS Message:\n\n" + message);
      }
  },

  _error: function(message) {
      if (this.shouldDebug) {
        cs.debugalert("AjaxJS ERROR:\n\n" + message);
      }
  }

};

