/**
 * @name /www/js/JK.js
 * @author Michael Foss <mfoss@clarionsafety.com>
 * @version 2007.07.02
 * 
 * Javascript Kit.
 */

///////////////////////////////////////////////////////////////////////////////
//                  B A S I C   F U N C T I O N A L I T Y                    //
///////////////////////////////////////////////////////////////////////////////

/**
 * JK Namespace.
 */

/**
 * HOW TO USE AjaxSend / AjaxReceive:
 *
 * var AjaxSendId = JK.AjaxSend(page, params[, handler]) -->
 *    send params to page, along with param Ajax=true;
 *    executes returned code upon response, and if handler was
 *    passed, also runs function with Ajax response as first parameter
 * JK.AjaxIsWaiting(AjaxSendId) --> true if still waiting
 * JK.AjaxHasResponded(AjaxSendId) --> true if already responded
 * JK.AjaxWasCancelled(AjaxSendId) --> true if was cancelled
 * JK.AjaxCancel(AjaxSendId) --> true upon successfully cancelling
 */

JK = {
  /**
   * Used to track AjaxSends; AjaxSend gives an id and sets the
   * appropriate cell to __AJAX_WAITING_FOR_RESPONSE; when function
   * is called, cell is set to __AJAX_RESPONDED; if AjaxCancel is
   * called before a response, cell is set to __AJAX_CANCELLED.
   */
  '__AJAX_WAITING_FOR_RESPONSE': 1,
  '__AJAX_RESPONDED': 2,
  '__AJAX_CANCELLED': 3,
  /**
   * @var integer[] AjaxSends
   */
  'AjaxSends': [],

  /**
   * Debugs the value of a variable by showing the value in an alert.
   *
   * @param x
   */
  'Debug': function(x) {
    var i, s = '';
    if (x && (this.IsArray(x) || typeof x == 'object') && typeof x != 'function')  {
      for (i in x)
        if (x[i] && typeof x[i] != 'function')
          s += i + ': ' + x[i] + '\n';
    }
    else
      s += x;
    alert(s);
  },

  /**
    * Returns true if el is an array, false otherwise.
    *
    * @param mixed el
    * @return boolean
    **/
  'IsArray': function(el) {
    return (this.IsObject(el) && el.constructor == Array);
  },

  /**
    * Returns true if el is an object, false otherwise.
    *
    * @param mixed el
    * @return boolean
    **/
  'IsObject': function(el) {
    return (el && typeof el == 'object') || typeof el == 'function';
  },

  /**
    * Returns true if val is in array arr, false otherwise;
    * as an item in the array arr, an object with the field
    * 'Range' can be specified and having the string value
    * in the format of comma-delimited values, separating
    * either individual values or ranges of values
    * (delimited by dashes); if a range is specified, an
    * optional field 'SortBy' can be specified that tells
    * the range type (default is 'integer', can be 'string');
    * an example of arr could be
    * [1, 2, 3, {'Range': '5-8,12,15,18-20,22', 'SortBy': 'integer'}].
    *
    * @param scalar val
    * @param array arr
    * @return boolean
    **/
  'InArray': function(val, arr) {
    if (this.IsObject(val) || !this.IsArray(arr))
      return false;
    if (!arr.length)
      return false;
    var i, j, k, tempArr, tempArr2;
    for (i = 0; i < arr.length; i++)
      // Is a range
      if (this.IsObject(arr[i]) && arr[i].Range) {
        tempArr = arr[i].Range.split(',');
        tempArr2 = [];
        for (j = 0; j < tempArr.length; j++)
          tempArr2[j] = tempArr[j].split('-');
        for (j = 0; j < tempArr2.length; j++) {
          switch (tempArr2[j].length) {
            // No "-" range found
            case 1:
              if (tempArr2[j][0] == val)
                return true;
              break;
            // "-" range found
            case 2:
              if (arr[i].SortBy != 'string') {
                tempArr2[j][0] *= 1;
                tempArr2[j][1] *= 1;
              }
              if (tempArr2[j][0] <= tempArr2[j][1])
                for (k = tempArr2[j][0]; k <= tempArr2[j][1]; k++)
                  if (val == k)
                    return true;
              break;
            // Multiple "-" range found
            default:
              return false;
              break;
          }
        }
      }
      // Is a simple scalar value
      else
        if (arr[i] == val)
          return true;
    return false;
  },

  /**
    * Send an Ajax call to "page" with &-delimited key/value pairs (delimited
    * by equal sign); upon loading the page, calls handler.
    *
    * @param string page
    * @param string params
    * @param function handler
    * @return boolean
    **/
  'AjaxSend': function(page, params, handler) {
    /* Start XHConn Function */
    /** XHConn - Simple XMLHTTP Interface - bfults@gmail.com - 2005-04-08        **
     ** Code licensed under Creative Commons Attribution-ShareAlike License      **
     ** http://creativecommons.org/licenses/by-sa/2.0/                           **
     ** Modified by Michael Foss (mfoss@safetylabel.com)                         **/
    if (handler === undefined)
      handler = function(responseCode) {
        try {
          eval(responseCode);
        }
        catch (e) {
          alert(e.message);
          alert(responseCode);
        }
      }
    var XHConn = function(ajaxSendId) {
      var xmlhttpWrapper = {}, bComplete = false;
      try { xmlhttpWrapper.xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); }
      catch (e) {
        try { xmlhttpWrapper.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); }
        catch (e) {
          try { xmlhttpWrapper.xmlhttp = new XMLHttpRequest(); }
          catch (e) { xmlhttpWrapper.xmlhttp = false; }
        }
      }
      if (!xmlhttpWrapper.xmlhttp)
        return null;
      xmlhttpWrapper.AjaxSendId = ajaxSendId;
      this.connect = function(sURL, sMethod, sVars, fnDone) {
        if (sVars.substring(0, 4) != 'Ajax' && sVars.indexOf('&Ajax=') == -1)
          sVars = 'Ajax=true&' + sVars;
        if (!xmlhttpWrapper || !xmlhttpWrapper.xmlhttp)
          return false;
        bComplete = false;
        sMethod = sMethod.toUpperCase();
        try {
          if (sMethod == "GET") {
            xmlhttpWrapper.xmlhttp.open(sMethod, sURL + "?" + sVars, true);
            sVars = "";
          }
          else {
            xmlhttpWrapper.xmlhttp.open(sMethod, sURL, true);
            xmlhttpWrapper.xmlhttp.setRequestHeader("Method", "POST "+sURL+" HTTP/1.1");
            xmlhttpWrapper.xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
          }
          xmlhttpWrapper.xmlhttp.onreadystatechange = function() {
            if (xmlhttpWrapper.xmlhttp.readyState == 4 && !bComplete) {
              bComplete = true;
              fnDone(xmlhttpWrapper);
            }
          };
          xmlhttpWrapper.xmlhttp.send(sVars);
        }
        catch(z) { return false; }
        return true;
      };
      return this;
    };
    /* End XHConn Function */

    JK.AjaxSends[JK.AjaxSends.length] = JK.__AJAX_WAITING_FOR_RESPONSE;
    var ajaxSendId = JK.AjaxSends.length - 1;
    var myConn = new XHConn(ajaxSendId);
    if (!myConn) {
      this.ThrowError("XMLHTTP not available. Try a newer/better browser.");
      return -1;
    }
    myConn.connect(page, "POST", params, function(oXMLWrapper) {
      if (JK.AjaxIsWaiting(oXMLWrapper.AjaxSendId)) {
        handler(oXMLWrapper.xmlhttp.responseText);
        JK.AjaxSends[oXMLWrapper.xmlhttp.AjaxSendId] == JK.__AJAX_RESPONDED;
      }
    } );
    return ajaxSendId;
  },

  /**
    * Returns true if ajax send is still waiting for a response.
    *
    * @param integer ajaxSendId
    * @return boolean
    */
  'AjaxIsWaiting': function(ajaxSendId) {
    return (JK.AjaxSends[ajaxSendId] == JK.__AJAX_WAITING_FOR_RESPONSE);
  },

  /**
    * Returns true if ajax send was responded to.
    *
    * @param integer ajaxSendId
    * @return boolean
    */
  'AjaxHasResponded': function(ajaxSendId) {
    return (JK.AjaxSends[ajaxSendId] == JK.__AJAX_RESPONDED);
  },

  /**
    * Returns true if ajax send was cancelled.
    *
    * @param integer ajaxSendId
    * @return boolean
    */
  'AjaxWasCancelled': function(ajaxSendId) {
    return (JK.AjaxSends[ajaxSendId] == JK.__AJAX_CANCELLED);
  },

  /**
    * Cancels an ajax send's handler if it's waiting.
    *
    * @param integer ajaxSendId
    * @return boolean
    */
  'AjaxCancel': function(ajaxSendId) {
    if (!JK.AjaxIsWaiting(ajaxSendId))
      return false;
    JK.AjaxSends[ajaxSendId] = JK.__AJAX_CANCELLED;
    return true;
  },

  /**
    * Returns value of selected <select> if selected, undefined otherwise.
    *
    * @param select select
    * @return string_or_undefined
    **/
  'GetSelectValue': function(select) {
    if (select == undefined)
      return undefined;
    else if (select.options == undefined)
      select = this.ById(select);
    if (select == undefined || select.options == undefined || select.selectedIndex == -1)
      return undefined;
    else
      return select.options[select.selectedIndex].value;
  },

  /**
    * Returns the DOM object whose id is given by id, false if it doesn't exist.
    *
    * @param string id
    * @return element_or_false
    **/
  'ById': function(id) {
    var el = document.getElementById(id);
    return (el ? el : false);
  },

  /**
    * Returns the DOM objects whose class name is given by className,
    * false if no elements exist, starting at elementToSearch;
    * will recurse only depth nodes deep.
    *
    * @param elementToSearch
    * @param className
    * @param depth
    * @return element[]_or_false
    **/
  'ByClass': function(elementToSearch, className, depth) {
    var customCollection = [];
    var count = 0;
    var temp;
    var i;
    if (!elementToSearch)
      return false;
    if (depth == undefined || typeof depth == 'object')
      depth = 1;
    // Check the current element
    if (elementToSearch.className == className) {
      customCollection[count] = elementToSearch;
      count++;
    }
    // Check the elements children, if it has any
    if (elementToSearch.childNodes && elementToSearch.childNodes.length && depth) {
      var i;
      for (i = 0; i < elementToSearch.childNodes.length; i++) {
        // Get a child's collection
        temp = this.ByClass(elementToSearch.childNodes[i], className, depth - 1);
        // If it has a collection, merge w/the current collection
        if (this.IsArray(temp)) {
          var j;
          for (j = 0; j < temp.length; j++) {
            customCollection[count] = temp[j];
            count++;
          }
        }
      }
    }
    return (count ? customCollection : false);
  },

  /**
    * Returns the DOM objects whose tag names are given by tagName,
    * false if none exist.
    *
    * @param string tagName
    * @return element[]_or_false
    **/
  'ByTag': function(tagName) {
    var els = document.getElementsByTagName(tagName);
    return (els ? els : false);
  },

  /**
    * Attachs an event "type" to element "el" with a
    * handler function "handler."
    *
    * @param element el
    * @param string type
    * @param function handler
    **/
  'AddEvent': function(el, type, handler) {
    if (!el)
      return false;
    if (el.addEventListener)
      el.addEventListener(type, handler, false);
    else if (el.attachEvent)
      el.attachEvent("on" + type, handler);
    else
      eval('el.on' + type + ' = handler;');
  },

  /**
    * Creates a select element with options given in the options
    * object, with values as fields and displayed text as values.
    *
    * @param object options
    * @return select_element
    **/
  'CreateSelect': function(options) {
    var x;
    var s = document.createElement('select');
    var e;
    for (x in options) {
      e = document.createElement('option');
      e.value = x;
      e.innerHTML = options[x];
      s.appendChild(e);
    }
    s.selectedIndex = 0;
    return s;
  },

  /**
    * HELPER FUNCTION FOR FUNCTIONS BELOW
    * Operates on the element or elements in elementsToOperateOn
    * if the value selected in selectElement is equal to one of
    * the values in operateValues; if the operation is not
    * completed, the opposite of it will be completed
    * (ie: if !Show, Hide); returns true if completed, false if not.
    *
    * @param select selectElement
    * @param string_or_string[] operateValues
    * @param element_or_element[] elementsToOperateOn
    * @param string operation ( Show | Hide | Disable | Enable )
    * @return boolean
    **/
  '_OperateOnElementIfValueSelected': function(selectElement, operateValues, elementsToOperateOn, operation) {
    var found = false;
    // Find what we have to match against
    var selectValue;
    if (selectElement.selectedIndex == -1)
      selectValue = -1;
    else
      selectValue = selectElement.options[selectElement.selectedIndex].value;

    // Match against either operateValues or values in operateValues[]
    if (JK.IsArray(operateValues)) {
      var i;
      found = (operateValues.length > 0);
      for (i = 0; i < operateValues.length && found; i++)
        found = (selectValue != operateValues[i]);
    }
    else
      found = (selectValue != operateValues);

    // Operate
    switch (operation) {
      case 'Hide':
        if (JK.IsArray(elementsToOperateOn))
          for (i = 0; i < elementsToOperateOn.length; i++)
            elementsToOperateOn[i].style.visibility = (found ? 'visible' : 'hidden');
        else
          elementsToOperateOn.style.visibility = (found ? 'visible' : 'hidden');
        break;

      case 'Show':
        if (JK.IsArray(elementsToOperateOn))
          for (i = 0; i < elementsToOperateOn.length; i++)
            elementsToOperateOn[i].style.visibility = (found ? 'hidden' : 'visible');
        else
          elementsToOperateOn.style.visibility = (found ? 'hidden' : 'visible');
        break;

      case 'Disable':
        if (JK.IsArray(elementsToOperateOn))
          for (i = 0; i < elementsToOperateOn.length; i++)
            elementsToOperateOn[i].disabled = !found;
        else
          elementsToOperateOn.disabled = !found;
        break;

      case 'Enable':
        if (JK.IsArray(elementsToOperateOn))
          for (i = 0; i < elementsToOperateOn.length; i++)
            elementsToOperateOn[i].disabled = found;
        else
          elementsToOperateOn.disabled = found;
        break;
    }

    // What happened?
    return found;
  },

  /**
    * Hides elements elementsToHide if the value of selectElement
    * is not equal to a value in hideValues; returns true if
    * successfully hidden, false otherwise.
    *
    * @param select selectElement
    * @param string_or_string[] hideValues
    * @param element_or_elements[] elementsToHide
    * @return boolean
    */
  'HideElementsIfValueSelected': function(selectElement, hideValues, elementsToHide) {
    return JK._OperateOnElementIfValueSelected(selectElement, hideValues, elementsToHide, 'Hide');
  },

  /**
    * Shows elements elementsToShow if the value of selectElement
    * is not equal to a value in showValues; returns true if
    * successfully shown, false otherwise.
    *
    * @param select selectElement
    * @param string_or_string[] showValues
    * @param element_or_elements[] elementsToShow
    * @return boolean
    */
  'ShowElementsIfValueSelected': function(selectElement, showValues, elementsToShow) {
    return JK._OperateOnElementIfValueSelected(selectElement, showValues, elementsToShow, 'Show');
  },

  /**
    * Disables elements elementsToDisable if the value of selectElement
    * is not equal to a value in disableValues; returns true if
    * successfully disabled, false otherwise.
    *
    * @param select selectElement
    * @param string_or_string[] disableValues
    * @param element_or_elements[] elementsToDisable
    * @return boolean
    */
  'DisableElementsIfValueSelected': function(selectElement, disableValues, elementsToDisable) {
    return JK._OperateOnElementIfValueSelected(selectElement, disableValues, elementsToDisable, 'Disable');
  },

  /**
    * Enables elements elementsToEnable if the value of selectElement
    * is not equal to a value in enableValues; returns true if
    * successfully enabled, false otherwise.
    *
    * @param select selectElement
    * @param string_or_string[] enableValues
    * @param element_or_elements[] elementsToEnable
    * @return boolean
    */
  'EnableElementsIfValueSelected': function(selectElement, enableValues, elementsToEnable) {
    return JK._OperateOnElementIfValueSelected(selectElement, enableValues, elementsToEnable, 'Enable');
  },

  /**
    *
    **/
  'ThrowError': function(errMsg) {
    if (console)
      (console.log('Error Thrown: ' + errMsg));
  },

  /**
    * Will create a new element based on tag;
    * given object attribs, will add attribues based on key/value pairs
    * TODO - give ability to add attributes.
    *
    * @param string tag
    * @param object attribs
    * @return element
    */
  'CreateElement': function(tag, attribs) {
    var el = document.createElement(tag);
    return el;
  },

  /**
    * Given an element el, returns the
    * position on the page in x/y coordinates.
    *
    * @param element el
    * @return integer[x,y]
    */
  'GetElementPosition': function(el) {
    if (!el || !el.offsetLeft)
      return false;
    var x = 0, y = 0;
    while (el != null) {
      x += el.offsetLeft;
      y += el.offsetTop;
      el = el.offsetParent;
    }
    return [x, y];
  },

  /**
    * Runs on body load.
    */
  'OnLoad': function() {
    // Stub for document.body.onload event.
    if (document.OnLoad)
      document.OnLoad();
  }
};