/**
 * Copyright (c)2005-2007 Matt Kruse (javascripttoolbox.com)
 * 
 * Dual licensed under the MIT and GPL licenses. 
 * This basically means you can use this code however you want for
 * free, but don't claim to have written it yourself!
 * Donations always accepted: http://www.JavascriptToolbox.com/donate/
 * 
 * Please do not link to the .js files on javascripttoolbox.com from
 * your site. Copy the files locally to your server instead.
 * 
 */
/* ******************************************************************* */
/*   UTIL FUNCTIONS                                                    */
/* ******************************************************************* */
var Util = {'$VERSION':1.06};

// Util functions - these are GLOBAL so they
// look like built-in functions.

// Determine if an object is an array
function isArray(o) {
  return (o!=null && typeof(o)=="object" && typeof(o.length)=="number" && (o.length==0 || defined(o[0])));
};

// Determine if an object is an Object
function isObject(o) {
  return (o!=null && typeof(o)=="object" && defined(o.constructor) && o.constructor==Object && !defined(o.nodeName));
};

// Determine if a reference is defined
function defined(o) {
  return (typeof(o)!="undefined");
};

// Iterate over an array, object, or list of items and run code against each item
// Similar functionality to Perl's map() function
function map(func) {
  var i,j,o;
  var results = [];
  if (typeof(func)=="string") {
    func = new Function('$_',func);
  }
  for (i=1; i<arguments.length; i++) {
    o = arguments[i];
    if (isArray(o)) {
      for (j=0; j<o.length; j++) {
        results[results.length] = func(o[j]);
      }
    }
    else if (isObject(o)) {
      for (j in o) {
        results[results.length] = func(o[j]);
      }
    }
    else {
      results[results.length] = func(o);
    }
  }
  return results;
};

// Set default values in an object if they are undefined
function setDefaultValues(o,values) {
  if (!defined(o) || o==null) {
    o = {};
  }
  if (!defined(values) || values==null) {
    return o;
  }
  for (var val in values) {
    if (!defined(o[val])) {
      o[val] = values[val];
    }
  }
  return o;
};

/* ******************************************************************* */
/*   DEFAULT OBJECT PROTOTYPE ENHANCEMENTS                             */
/* ******************************************************************* */
// These functions add useful functionality to built-in objects
Array.prototype.contains = function(o) {
  var i,l;
  if (!(l = this.length)) { return false; }
  for (i=0; i<l; i++) {
    if (o==this[i]) {
      return true;
    }
  }
};

/* ******************************************************************* */
/*   DOM FUNCTIONS                                                     */
/* ******************************************************************* */
var DOM = (function() { 
  var dom = {};
  
  // Get a parent tag with a given nodename
  dom.getParentByTagName = function(o,tagNames) {
    if(o==null) { return null; }
    if (isArray(tagNames)) {
      tagNames = map("return $_.toUpperCase()",tagNames);
      while (o=o.parentNode) {
        if (o.nodeName && tagNames.contains(o.nodeName)) {
          return o;
        }
      }
    }
    else {
      tagNames = tagNames.toUpperCase();
      while (o=o.parentNode) {
        if (o.nodeName && tagNames==o.nodeName) {
          return o;
        }
      }
    }
    return null;
  };
  
  // Remove a node from its parent
  dom.removeNode = function(o) {
    if (o!=null && o.parentNode && o.parentNode.removeChild) {
      // First remove all attributes which are func references, to avoid memory leaks
      for (var i in o) {
        if (typeof(o[i])=="function") {
          o[i] = null;
        }
      }
      o.parentNode.removeChild(o);
      return true;
    }
    return false;
  };

  // Get the outer width in pixels of an object, including borders, padding, and margin
  dom.getOuterWidth = function(o) {
    if (defined(o.offsetWidth)) {
      return o.offsetWidth;
    }
    return null;
  };

  // Get the outer height in pixels of an object, including borders, padding, and margin
  dom.getOuterHeight = function(o) {
    if (defined(o.offsetHeight)) {
      return o.offsetHeight;
    }
    return null;
  };

  // Resolve an item, an array of items, or an object of items
  dom.resolve = function() {
    var results = new Array();
    var i,j,o;
    for (var i=0; i<arguments.length; i++) {
      var o = arguments[i];
      if (o==null) {
        if (arguments.length==1) {
          return null;
        }
        results[results.length] = null;
      }
      else if (typeof(o)=='string') {
        if (document.getElementById) {
          o = document.getElementById(o);
        }
        else if (document.all) {
          o = document.all[o];
        }
        if (arguments.length==1) {
          return o;
        }
        results[results.length] = o;
      }
      else if (isArray(o)) {
        for (j=0; j<o.length; j++) {
          results[results.length] = o[j];
        }
      }
      else if (isObject(o)) {
        for (j in o) {
          results[results.length] = o[j];
        }
      }
      else if (arguments.length==1) {
        return o;
      }
      else {
        results[results.length] = o;
      }
    }
    return results;
  };
  dom.$ = dom.resolve;
  
  return dom;
})();

/* ******************************************************************* */
/*   CSS FUNCTIONS                                                     */
/* ******************************************************************* */
var CSS = (function(){
  var css = {};

  // Convert an RGB string in the form "rgb (255, 255, 255)" to "#ffffff"
  css.rgb2hex = function(rgbString) {
    if (typeof(rgbString)!="string" || !defined(rgbString.match)) { return null; }
    var result = rgbString.match(/^\s*rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*/);
    if (result==null) { return rgbString; }
    var rgb = +result[1] << 16 | +result[2] << 8 | +result[3];
    var hex = "";
    var digits = "0123456789abcdef";
    while(rgb!=0) { 
      hex = digits.charAt(rgb&0xf)+hex; 
      rgb>>>=4; 
    } 
    while(hex.length<6) { hex='0'+hex; }
    return "#" + hex;
  };

  // Convert hyphen style names like border-width to camel case like borderWidth
  css.hyphen2camel = function(property) {
    if (!defined(property) || property==null) { return null; }
    if (property.indexOf("-")<0) { return property; }
    var str = "";
    var c = null;
    var l = property.length;
    for (var i=0; i<l; i++) {
      c = property.charAt(i);
      str += (c!="-")?c:property.charAt(++i).toUpperCase();
    }
    return str;
  };
  
  // Determine if an object or class string contains a given class.
  css.hasClass = function(obj,className) {
    if (!defined(obj) || obj==null || !RegExp) { return false; }
    var re = new RegExp("(^|\\s)" + className + "(\\s|$)");
    if (typeof(obj)=="string") {
      return re.test(obj);
    }
    else if (typeof(obj)=="object" && obj.className) {
      return re.test(obj.className);
    }
    return false;
  };
  
  // Add a class to an object
  css.addClass = function(obj,className) {
    if (typeof(obj)!="object" || obj==null || !defined(obj.className)) { return false; }
    if (obj.className==null || obj.className=='') { 
      obj.className = className; 
      return true; 
    }
    if (css.hasClass(obj,className)) { return true; }
    obj.className = obj.className + " " + className;
    return true;
  };
  
  // Remove a class from an object
  css.removeClass = function(obj,className) {
    if (typeof(obj)!="object" || obj==null || !defined(obj.className) || obj.className==null) { return false; }
    if (!css.hasClass(obj,className)) { return false; }
    var re = new RegExp("(^|\\s+)" + className + "(\\s+|$)");
    obj.className = obj.className.replace(re,' ');
    return true;
  };
  
  // Fully replace a class with a new one
  css.replaceClass = function(obj,className,newClassName) {
    if (typeof(obj)!="object" || obj==null || !defined(obj.className) || obj.className==null) { return false; }
    css.removeClass(obj,className);
    css.addClass(obj,newClassName);
    return true;
  };
  
  // Get the currently-applied style of an object
  css.getStyle = function(o, property) {
    if (o==null) { return null; }
    var val = null;
    var camelProperty = css.hyphen2camel(property);
    // Handle "float" property as a special case
    if (property=="float") {
      val = css.getStyle(o,"cssFloat");
      if (val==null) { 
        val = css.getStyle(o,"styleFloat"); 
      }
    }
    else if (o.currentStyle && defined(o.currentStyle[camelProperty])) {
      val = o.currentStyle[camelProperty];
    }
    else if (window.getComputedStyle) {
      val = window.getComputedStyle(o,null).getPropertyValue(property);
    }
    else if (o.style && defined(o.style[camelProperty])) {
      val = o.style[camelProperty];
    }
    // For color values, make the value consistent across browsers
    // Convert rgb() colors back to hex for consistency
    if (/^\s*rgb\s*\(/.test(val)) {
      val = css.rgb2hex(val);
    }
    // Lowercase all #hex values
    if (/^#/.test(val)) {
      val = val.toLowerCase();
    }
    return val;
  };
  css.get = css.getStyle;

  // Set a style on an object
  css.setStyle = function(o, property, value) {
    if (o==null || !defined(o.style) || !defined(property) || property==null || !defined(value)) { return false; }
    if (property=="float") {
      o.style["cssFloat"] = value;
      o.style["styleFloat"] = value;
    }
    else if (property=="opacity") {
      o.style['-moz-opacity'] = value;
      o.style['-khtml-opacity'] = value;
      o.style.opacity = value;
      if (defined(o.style.filter)) {
        o.style.filter = "alpha(opacity=" + value*100 + ")";
      }
    }
    else {
      o.style[css.hyphen2camel(property)] = value;
    }
    return true;
  };
  css.set = css.setStyle;
  
  // Get a unique ID which doesn't already exist on the page
  css.uniqueIdNumber=1000;
  css.createId = function(o) {
    if (defined(o) && o!=null && defined(o.id) && o.id!=null && o.id!="") { 
      return o.id;
    }
    var id = null;
    while (id==null || document.getElementById(id)!=null) {
      id = "ID_"+(css.uniqueIdNumber++);
    }
    if (defined(o) && o!=null && (!defined(o.id)||o.id=="")) {
      o.id = id;
    }
    return id;
  };
  
  return css;
})();

/* ******************************************************************* */
/*   EVENT FUNCTIONS                                                   */
/* ******************************************************************* */

var Event = (function(){
  var ev = {};
  
  // Resolve an event using IE's window.event if necessary
  // --------------------------------------------------------------------
  ev.resolve = function(e) {
    if (!defined(e) && defined(window.event)) {
      e = window.event;
    }
    return e;
  };
  
  // Add an event handler to a function
  // Note: Don't use 'this' within functions added using this method, since
  // the attachEvent and addEventListener models differ.
  // --------------------------------------------------------------------
  ev.add = function( obj, type, fn, capture ) {
    if (obj.addEventListener) {
      obj.addEventListener( type, fn, capture );
      return true;
    }
    else if (obj.attachEvent) {
      obj.attachEvent( "on"+type, fn );
      return true;
    }
    return false;
  };

  // Get the mouse position of an event
  // --------------------------------------------------------------------
  // PageX/Y, where they exist, are more reliable than ClientX/Y because 
  // of some browser bugs in Opera/Safari
  ev.getMouseX = function(e) {
    e = ev.resolve(e);
    if (defined(e.pageX)) {
      return e.pageX;
    }
    if (defined(e.clientX)) {
      return e.clientX+Screen.getScrollLeft();
    }
    return null;
  };
  ev.getMouseY = function(e) {
    e = ev.resolve(e);
    if (defined(e.pageY)) {
      return e.pageY;
    }
    if (defined(e.clientY)) {
      return e.clientY+Screen.getScrollTop();
    }
    return null;
  };

  // Stop the event from bubbling up to parent elements.
  // Two method names map to the same function
  // --------------------------------------------------------------------
  ev.cancelBubble = function(e) {
    e = ev.resolve(e);
    if (typeof(e.stopPropagation)=="function") { e.stopPropagation(); } 
    if (defined(e.cancelBubble)) { e.cancelBubble = true; }
  };
  ev.stopPropagation = ev.cancelBubble;

  // Prevent the default handling of the event to occur
  // --------------------------------------------------------------------
  ev.preventDefault = function(e) {
    e = ev.resolve(e);
    if (typeof(e.preventDefault)=="function") { e.preventDefault(); } 
    if (defined(e.returnValue)) { e.returnValue = false; }
  };
  
  return ev;
})();

/* ******************************************************************* */
/*   SCREEN FUNCTIONS                                                  */
/* ******************************************************************* */
var Screen = (function() {
  var screen = {};

  // Get a reference to the body
  // --------------------------------------------------------------------
  screen.getBody = function() {
    if (document.body) {
      return document.body;
    }
    if (document.getElementsByTagName) {
      var bodies = document.getElementsByTagName("BODY");
      if (bodies!=null && bodies.length>0) {
        return bodies[0];
      }
    }
    return null;
  };

  // Get the amount that the main document has scrolled from top
  // --------------------------------------------------------------------
  screen.getScrollTop = function() {
    if (document.documentElement && defined(document.documentElement.scrollTop) && document.documentElement.scrollTop>0) {
      return document.documentElement.scrollTop;
    }
    if (document.body && defined(document.body.scrollTop)) {
      return document.body.scrollTop;
    }
    return null;
  };
  
  // Get the amount that the main document has scrolled from left
  // --------------------------------------------------------------------
  screen.getScrollLeft = function() {
    if (document.documentElement && defined(document.documentElement.scrollLeft) && document.documentElement.scrollLeft>0) {
      return document.documentElement.scrollLeft;
    }
    if (document.body && defined(document.body.scrollLeft)) {
      return document.body.scrollLeft;
    }
    return null;
  };
  
  // Util function to default a bad number to 0
  // --------------------------------------------------------------------
  screen.zero = function(n) {
    return (!defined(n) || isNaN(n))?0:n;
  };

  // Get the width of the entire document
  // --------------------------------------------------------------------
  screen.getDocumentWidth = function() {
    var width = 0;
    var body = screen.getBody();
    if (document.documentElement && (!document.compatMode || document.compatMode=="CSS1Compat")) {
        var rightMargin = parseInt(CSS.get(body,'marginRight'),10) || 0;
        var leftMargin = parseInt(CSS.get(body,'marginLeft'), 10) || 0;
      width = Math.max(body.offsetWidth + leftMargin + rightMargin, document.documentElement.clientWidth);
    }
    else {
      width =  Math.max(body.clientWidth, body.scrollWidth);
    }
    if (isNaN(width) || width==0) {
      width = screen.zero(self.innerWidth);
    }
    return width;
  };
  
  // Get the height of the entire document
  // --------------------------------------------------------------------
  screen.getDocumentHeight = function() {
    var body = screen.getBody();
    var innerHeight = (defined(self.innerHeight)&&!isNaN(self.innerHeight))?self.innerHeight:0;
    if (document.documentElement && (!document.compatMode || document.compatMode=="CSS1Compat")) {
        var topMargin = parseInt(CSS.get(body,'marginTop'),10) || 0;
        var bottomMargin = parseInt(CSS.get(body,'marginBottom'), 10) || 0;
      return Math.max(body.offsetHeight + topMargin + bottomMargin, document.documentElement.clientHeight, document.documentElement.scrollHeight, screen.zero(self.innerHeight));
    }
    return Math.max(body.scrollHeight, body.clientHeight, screen.zero(self.innerHeight));
  };
  
  // Get the width of the viewport (viewable area) in the browser window
  // --------------------------------------------------------------------
  screen.getViewportWidth = function() {
    if (document.documentElement && (!document.compatMode || document.compatMode=="CSS1Compat")) {
      return document.documentElement.clientWidth;
    }
    else if (document.compatMode && document.body) {
      return document.body.clientWidth;
    }
    return screen.zero(self.innerWidth);
  };
  
  // Get the height of the viewport (viewable area) in the browser window
  // --------------------------------------------------------------------
  screen.getViewportHeight = function() {
    if (!window.opera && document.documentElement && (!document.compatMode || document.compatMode=="CSS1Compat")) {
      return document.documentElement.clientHeight;
    }
    else if (document.compatMode && !window.opera && document.body) {
      return document.body.clientHeight;
    }
    return screen.zero(self.innerHeight);
  };

  return screen;
})();var Sort = (function(){
  var sort = {};
  sort.AlphaNumeric = function(a,b) {
    if (a==b) { return 0; }
    if (a<b) { return -1; }
    return 1;
  };

  sort.Default = sort.AlphaNumeric;
  
  sort.NumericConversion = function(val) {
    if (typeof(val)!="number") {
      if (typeof(val)=="string") {
        val = parseFloat(val.replace(/,/g,''));
        if (isNaN(val) || val==null) { val=0; }
      }
      else {
        val = 0;
      }
    }
    return val;
  };
  
  sort.Numeric = function(a,b) {
    return sort.NumericConversion(a)-sort.NumericConversion(b);
  };

  sort.IgnoreCaseConversion = function(val) {
    if (val==null) { val=""; }
    return (""+val).toLowerCase();
  };

  sort.IgnoreCase = function(a,b) {
    return sort.AlphaNumeric(sort.IgnoreCaseConversion(a),sort.IgnoreCaseConversion(b));
  };

  sort.CurrencyConversion = function(val) {
    if (typeof(val)=="string") {
      val = val.replace(/^[^\d\.]/,'');
    }
    return sort.NumericConversion(val);
  };
  
  sort.Currency = function(a,b) {
    return sort.Numeric(sort.CurrencyConversion(a),sort.CurrencyConversion(b));
  };
  
  sort.DateConversion = function(val) {
    // inner util function to parse date formats
    function getdate(str) {
      // inner util function to convert 2-digit years to 4
      function fixYear(yr) {
        yr = +yr;
        if (yr<50) { yr += 2000; }
        else if (yr<100) { yr += 1900; }
        return yr;
      };
      var ret;
      // YYYY-MM-DD
      if (ret=str.match(/(\d{2,4})-(\d{1,2})-(\d{1,2})/)) {
        return (fixYear(ret[1])*10000) + (ret[2]*100) + (+ret[3]);
      }
      // MM/DD/YY[YY] or MM-DD-YY[YY]
      if (ret=str.match(/(\d{1,2})[\/-](\d{1,2})[\/-](\d{2,4})/)) {
        return (fixYear(ret[3])*10000) + (ret[1]*100) + (+ret[2]);
      }
      return 99999999; // So non-parsed dates will be last, not first
    };
    return getdate(val);
  };

  sort.Date = function(a,b) {
    return sort.Numeric(sort.DateConversion(a),sort.DateConversion(b));
  };

  return sort;
})();