// an easy way to add new scripts from other scripts

// avoid wiping include list if this file is re-imported
var __includes__;
if (__includes__ == undefined) {
  __includes__ = ['/hub/js/utils.js'];
}
function include(scriptpath) {
  for (var i = 0; i < __includes__.length; i++)  // avoid circular & repetetive imports
    if (__includes__[i] == scriptpath)
      return;
  var html_doc = document.getElementsByTagName('head').item(0);
  var js = document.createElement('script');
  js.setAttribute('language', 'javascript');
  js.setAttribute('type', 'text/javascript');
  js.setAttribute('src', scriptpath);
  html_doc.appendChild(js);
  __includes__.push(scriptpath);
  return false;
}

// safely logs to javascript console
function jsLog() {
  try {
    console.log.apply(console, arguments);
  }
  catch(ex) {
    try {
      console.log(arguments[0]);  // IE8
    }
    catch(ex){}
  }
}

// usage: getElement(name as string) -> htmlnode object
// get an element by its id
// html node has to have e.g. 'id="name"'
function getElement(name) {
  var e;
  if (typeof name != 'string') e = name;      // the argument is an element itself, not a string?
  else if (document.getElementById)           // standard for most browsers
    e = document.getElementById(name);
  if (document.all) {
    var elem = e ? e: document.all[name];     // IE 7 || IE 5/6
    if (!elem) return;
    for (var k in Node.prototype)             // since IE has no Node/HTMLElement, and doesn't play nicely with custom
      if (typeof(elem[k]) == 'undefined') {   //   methods added to .prototype, we copy the methods into elements if we need to
        try { elem[k] = Node.prototype[k]; }
        catch(e) { }
      }
    return elem;
  }
  return e;
}


// usage: getElementsByClassName(classname as string, [root as element reference, tag as string]) -> elements as list
// get all elements with the css style class passed in
function getElementsByClassName(classname, root, tag) {
  if (typeof(root) != 'undefined')
    return root.getElementsByClassAndTag(classname, tag);
  return document.getElementsByClassAndTag(classname, tag);
}
 

function hasPlugin(name) {
  for (var i = 0; i < navigator.plugins.length; i++)
    if (navigator.plugins[i].name.toLowerCase().count(name.toLowerCase()))
      return true;
  return false;
}
// send/recv data using XmlHttpRequest 
// usage:
//   silent_sendrecv(url as string,
//                   query_string as string,
//                   xml_or_text as "text" or "xml",
//                   async_flag as bool, (currently unused)
//                   callbacks function,
//                   context, (can be used to pass context to the callback)
//                   get_or_post as "GET" or "POST") -> null
//
// note: when 'get_or_post' is omitted (null) or something else
// then 'post' or 'POST', it defaults to GET

// where my_func has signature fn(data as string) -> null

function silent_sendrecv(url, query_string, xml_or_text, async_flag, callback, context, get_or_post)
{
  if (get_or_post) {
    get_or_post = (get_or_post.toUpperCase() == 'POST' ? 'POST' : 'GET');
  } else {
    get_or_post = 'GET';
  }
  var req;
  if (window.XMLHttpRequest)
  {
    try { req = new XMLHttpRequest(); }
    catch(e) { req = false; }
  }
  else if(window.ActiveXObject)
  {
    try { req = new ActiveXObject("Msxml2.XMLHTTP"); }
    catch(e)
    {
      try { req = new ActiveXObject("Microsoft.XMLHTTP"); }
      catch(e) { req = false; }
    }
  }
  if(req)
  {
    var data;
    req.onreadystatechange = function()
    {
      if (req.readyState == 4)
      {
        if (req.status == 200) // only if "OK"
        {
          if (xml_or_text == "text")
            data = req.responseText;
          else
            data = req.responseXML;
          callback(data, context);
        }
        else
          data = null;
      }
    };
    // Normally, the async flag should be set to true (async), otherwise
    //   The javascript will block the UI until the response is complete,
    //   which is bad for laggy connections.
    
    if (get_or_post == 'POST') {
      req.open('POST', url, async_flag);
      req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
      req.send(query_string);
    } else {
      req.open('GET', url, async_flag);
      req.send(query_string);
    }
  }
}

function cancelEventBubble(event)
{
  if (window.event)
  {
    window.event.cancelBubble = true;
    window.event.returnValue = false;
  }
  else
  {
    event.stopPropagation(true);
    event.preventDefault(true);
  }
}

if (!window.eventLookup)
  window.eventLookup = {};

function addEvent(event, fn, obj)
{
  obj = obj ? obj : window;
  try { obj.addEventListener(event, fn, false) }
  catch (e) { obj.attachEvent('on'+event, fn) }
  fn_hash = makeEventName(event, obj, fn);
  window.eventLookup[fn_hash] = fn;  // add function for later removal
}

function removeEvent(event, fn, obj)
{
  obj = obj ? obj : window;
  fn_hash = makeEventName(event, obj, fn); // look up function
  if (fn_hash && window.eventLookup[fn_hash])
  {
    try
    {
      obj.removeEventListener(event, window.eventLookup[fn_hash], false);
    }
    catch (e)
    {
      obj.detachEvent('on'+event, window.eventLookup[fn_hash]);
    }
  }
}

function addLoadEvent(fn)
{
  addEvent('load', fn);
}

function makeEventName(event, obj, fn)
{
  return obj.toString() + ':' + event.toString() + ':' +fn.toString();
}



function swapClass(obj, class1, class2)
{
  e = typeof(obj) == 'string' ? getElement(obj) : obj;
  try
  {
    e.setAttribute('class', e.className == class1 ? class2 : class1) //Safari
    e.setAttribute('className',  e.className == class1 ? class2 : class1) //others
  }
  catch(err)
  {
    e.className = e.className ? class1 : class2
  }
}

function setClass(obj, classname)
{
  e = typeof(obj) == 'string' ? getElement(obj) : obj;
  try
  {
    e.setAttribute('class', classname) //Safari
    e.setAttribute('className', classname) //others
  }
  catch(err)
  {
    e.className = classname
  }
}

function getClassName(obj)
{
  obj = getElement(obj);  // allows you get a class name from an element id
  if (obj.className)
    return obj.className;
  try 
  {
    var cls = obj.getAttribute('class')
    if (cls)
      return cls;
    return obj.getAttribute('className')
  }
  catch(e){};
  return '';
}


// normalize Node across browers
Node = (function() {
  if (typeof(Node)=='undefined')
    return Object;  // IE
  else if (typeof(Node.prototype)!='undefined')
    return Node;    // Safari3, FF
  // Safari < 3
  var protA = document.createElement('a').__proto__;
  var protB = document.createElement('p').__proto__;
  if (! window.HTMLElement && (typeof document.createElement) == "function" && protA==protB) {
    window.HTMLElement = {}; // prevent people from constructing
    window.HTMLElement.prototype = protA;
    if (Node && typeof(Node.prototype)=='undefined') {
      Node.prototype = protA;
      return Node;
    }
  }
  return Object;
})();

// this is in here instead of prototypes.js to support the global funtction getElementsByClassName, which wraps it
Node.prototype.getElementsByClassAndTag = function(classname, tag)
{
  var outlist = [];
  var root = this;
  tag = typeof(tag) != typeof(undefined) ? tag : '*';
  var els = root.getElementsByTagName ? root.getElementsByTagName(tag) : [];
  var pattern = new RegExp("(^|\\s)" + classname + "(\\s|$)");
  for (var i = 0; i < els.length; i++)
    if (pattern.test(els[i].className))
      outlist.push(els[i]);
  return outlist;
};
document.getElementsByClassAndTag = Node.prototype.getElementsByClassAndTag;

Node.prototype.getElementsByTagAndAttrs = function(tag, attrs) {
  var firstset = this.getElementsByTagName ? this.getElementsByTagName(tag) : [];
  var out = [];
  if (! attrs) return firstset;
  for (var key in attrs)
    for (var i = 0; i < firstset.length; i++)
      if (firstset[i].getAttribute(key) == attrs[key])
        out.push(firstset[i])
  return out;
};
document.getElementsByTagAndAttrs = Node.prototype.getElementsByTagAndAttrs;

Node.prototype.getFirstAncestorNodeByTag = function(tag) {
  var elem = this;
  while (elem && elem.parentNode && elem.tagName && (elem.tagName.toLowerCase() != tag.toString().toLowerCase()))
    if (elem.parentNode)
      elem = elem.parentNode;
  return elem;
};
document.getFirstAncestorNodeByTag = Node.prototype.getFirstAncestorNodeByTag;

Node.prototype.absolutePosition = function() {
  var o = this;
  var ret = {top:0, left:0};
  var border, b;
  while (o.offsetParent) {
    border = 0;
    b = 0
    if (o.tagName.toLowerCase()=='table') {
      b = parseInt(o.border);
      if (! b)  b = 0
      border += b
    }
    if (o.style) {
      b = parseInt(o.style.borderWidth)
      if (! b)  b = 0
      border += b;
    }
    ret.top += o.offsetTop + border;
    ret.left += o.offsetLeft + border;
    o = o.offsetParent;
  }
  return ret;
};
document.absolutePosition = Node.prototype.absolutePosition;


// usage: mystring.toDict([item_sep as string, kvsep as string]) -> dict
// Parses 'this' string into a dictionary object
//string format as such: "key1:value1\nkey2:value2"
String.prototype.toDict = function(item_sep, kvsep)
{
  if (arguments.length == 0)
  {
    kvsep = ':';
    item_sep = '\n';
  }
  var lst = this.split(item_sep); // split along all items
  var ret = {};
  for (var i = 0; i < lst.length; ++i)
  {
    var n = lst[i].split(kvsep); // split along key-value sep
    k = n.shift()
    if (n.length > 1)     // correct for values with kvsep in the string
      v = n.join(kvsep);  // because .split(kvsep, 1) doesn't seem to work right
    else
      v = n.shift();
    ret[String(k)] = String(v);
  }
  return ret;
}

// usage: mystring.count(substr as string) -> int
// returns number of non-overlapping occurances of argument
String.prototype.count = function(substr)
{
  return this.split(substr).length - 1;
}

String.prototype.endswith = function(substr)
{
  if (substr.length > this.length) return false;
  return this.slice(this.length-substr.length, this.length) == substr;
}

String.prototype.startswith = function(substr)
{
  if (substr.length > this.length) return false;
  return this.slice(0, substr.length) == substr;
}

String.prototype.tmplfill = function() {
  var ar = this.split('$$', arguments.length);
  var s = ''
  for (var i = 0; i < ar.length; i++)
  { 
    s += ar[i] + arguments[i];
  }  
  return s;
}


// usage: myarray.indexOf(obj as object) -> int
// Returns 0-based index of any object type in array
// If the object is not found, returns -1
Array.prototype.indexOf = function(obj)
{
  for (var i = 0; i < this.length; ++i)
    if (this[i] == obj)
      return i;
  return -1; // not found
}

// usage: myarray.contains(obj as object) -> bool
// analogous to python 'in' operator when used as a test
Array.prototype.contains = function(obj)
{
  return this.indexOf(obj) > -1;
}


// usage: myarray.remove(obj as object) -> null
// Removes first instance of 'obj' in this
Array.prototype.remove = function(obj)
{
  this.splice(this.indexOf(obj), 1);
}

// usage: myarray.each(fn as function) -> null
// where function arg 1 is the value of the current iteration,
//   and arg 2 is a reference to the array which is being iterated
// function can be a named function or anonymous one
// automatically iterate over a sequence
// Example:
// [1,2,3,4,5].each(function(n) { alert(n) } );
// or
// [1,2,3,4,5].each(alert)
// both will work the same

Array.prototype.each = function(fn)
{
  for (var i=0; i < this.length;i++)
    fn(this[i], this);
}
Array.prototype.map = function (fn)
{
  var r = [];
  var l = this.length;
  for(i=0;i<l;i++)
    r.push(fn(this[i]));
  return r; 
};

window.getSize = function() {
  var w = 0, h = 0;
  if (typeof window.innerWidth == 'number') {
    w = this.innerWidth;
    h = this.innerHeight;
  }
  else if (document.documentElement &&
           (document.documentElement.clientWidth || document.documentElement.clientHeight)) {
    w = document.documentElement.clientWidth;
    h = document.documentElement.clientHeight;
  }
  else if (document.body && (document.body.clientWidth || document.body.clientHeight)) {
    w = document.body.clientWidth;
    h = document.body.clientHeight;
  }
  return {width:w,height:h};
}

// get any selected text on the current web page
// usage: getSelectedText([obj as form element]) -> string
function getSelectedText()
{
  if (arguments[0])
  {
    var o = arguments[0];
    if (document.selection) // for IE
    {
      var range = document.selection.createRange();
      var stored_range = range.duplicate();
      stored_range.moveToElementText(o);
      stored_range.setEndPoint('EndToEnd', range);
      o.selectionStart = stored_range.text.length - range.text.length;
      o.selectionEnd = o.selectionStart + range.text.length;
    }
    return (o.value).slice(o.selectionStart, o.selectionEnd);
  }
  if(window.getSelection)
    return window.getSelection();
  else if(document.getSelection)
    return document.getSelection();
  else if(document.selection)
    return document.selection.createRange().text;
  else return;
}

// usage e.g.:  <textarea ... onblur="this.value = clean_MSWord_chars(this.value)"></textarea>

function clean_MSWord_chars(input)
{
  // for a list of unicode values of various chars: http://www.cs.tut.fi/~jkorpela/www/windows-chars.html
  var swapCodes   = new Array(8211, 8212, 8216, 8217, 8220, 8221, 8226, 8230);  //, 8482);
  var swapStrings = new Array("-" , "--", "'" , "'" , '"' , '"' , "*" , "..."); //, " TM ");

  var output = ''+input;
  for (var i = 0; i < swapCodes.length; i++)
  {
    var swapper = new RegExp("\\u" + swapCodes[i].toString(16), "g"); // hex codes
    output = output.replace(swapper, swapStrings[i]);
  }
  return output;
}

// 'f' argument is a form reference, not form name
function clean_all_form_inputs(f)
{
  for (var i = 0; i < f.elements.length; ++i)
  {
    var elem = f.elements[i];
    if ((elem.nodeName.toLowerCase() == 'input' && elem.type == 'text') || elem.nodeName.toLowerCase()=='textarea')
      elem.value = clean_MSWord_chars(elem.value)
  }
}


function countChars(obj, maxlen)
{
  var f = obj.form;
  var fieldname = obj.name
  var display_field = f[fieldname+'max'];
  var num = obj.value.length;
  if (num > maxlen)
  {
    alert("Only " + maxlen + " characters are allowed in this field. Extra characters will be removed");
    obj.value = obj.value.substring(0, maxlen)
    num = maxlen;
  }
  display_field.value = num;
}

function countLines(obj, maxlines)
{
  var f = obj.form;
  var fieldname = obj.name
  var display_field = f[fieldname+'max'];
  var lines = obj.value.split('\n')
  var num = lines.length
  if (num > maxlines)
  {
    alert("Only " + maxlines + " lines are allowed in this field. Extra characters will be removed");
    obj.value = lines.slice(0, maxlines).join('\n')
    num = maxlines
  }
  display_field.value = num;
}

function countCharsPerLine(obj, maxchars, err_msg) {
  err_msg = err_msg || "Only $$ characters per line are allowed in this field. Extra characters will be removed";
  var f = obj.form
  var fieldname = obj.name;
  var display_field = f[fieldname+'max'];
  var lines = obj.value.split('\n');
  var lengths = [];
  for (var i = 0; i < lines.length; i++)
    lengths.push(lines[i].length);
  var max = Math.max.apply(null, lengths)
  var num = max;
  if (max > maxchars) {
    alert(err_msg.replace('$$', maxchars));
    for (var i = 0; i < lines.length; i++)
      lines[i] = lines[i].slice(0, maxchars)
    obj.value = lines.join('\n')
    var num = maxchars;
  }
  display_field.value = num;
}

function deleteOption(sel, optval) {
  // find the option
  for (var i = 0; i < sel.options.length; i++) {
    if (sel.options[i].value == optval) break
  }
  if (i >= sel.options.length) return
  // copy text/val from next option until end
  for (; i < sel.options.length - 1; i++) {
    sel.options[i].value = sel.options[i+1].value
    sel.options[i].text = sel.options[i+1].text
  }
  // remove last option
  sel.options.length--
}

// add functionality to form prototype
function setformvalue(frm, name, value) {
  fld = frm[name]
  if (!fld.name && fld.length) {
    // must be an InputCollection
    for (var i = 0; i < fld.length; i++) {
      if (fld[i].name == name) {
        fld[i].checked = fld[i].value == value
      }
    }
    return
  }
  if (fld.type == 'text' || fld.type == 'passwd' || fld.type == 'hidden') {
    fld.value = value
  }
  else if (fld.type == 'select-one' || fld.type == 'select-multiple') {
    i = 0;
    while (i < fld.options.length) {
      opt = fld.options[i];
      optval = opt.value || opt.text;
      if (optval == value) {
        fld.selectedIndex = i;
        opt.selected = true;
        break;
      }
      i++;
    }
  }
  else if (fld.type == 'checkbox') {
    fld.checked = value != '';
  }
  else {
    if (fld.type) {
      jsLog('not handled yet: '+fld.type+', name:'+fld.name);
      fld.value = value;
    }
    else {
      for (i=0; i< fld.length; i++) {
        if (fld[i].value == value) {
          fld[i].checked = true;
          break;
        }
      }
    }
  }
}

// validation functions
// used by option set editor
function validate_pyvar_lowercase(varname, label) {
  // return msg if given varname is not a valid python variable name
  // return null otherwise
  if (! label) {
    label = 'variable'
  }
  // [a-z][a-z0-9_]*
  var pypat =/^[a-z][a-z0-9_]*$/;
  if (! pypat.test(varname)) {
    return label + ' must be only lowercase letters, numbers, and underscore and start with a lowercase letter. e.g. ship_street1'
  }
}

function alert_pyvar_lowercase(event, input, label) {
  // usage: onChange="alert_pyvar_lowercase(event, this);"
  // if invalid, restores old value and focus to field
  if (! label) {
    label = input.name;
  }
  var msg = validate_pyvar_lowercase(input.value, label);
  if (msg) {
    alert(msg);
    input.value = ValidateOrig[input.name] || '';
    window.setTimeout( function() { input.focus() }, 50);
    return false;
  }
  return true;
}

var ValidateOrig = {};
function validate_store_orig(input) {
  // usage: onFocus="validate_store_orig(this);
  // store the original value when field is entered
  ValidateOrig[input.name] = input.value;
}

function getformvalue(frm, name) {
  fld = frm[name]
  if (fld+'' == 'undefined') return ''
  if (fld.type == 'text' || fld.type == 'passwd' || fld.type == 'hidden') {
    return fld.value
  } else if (fld.type == 'select-one' || fld.type == 'select-multiple') {
    var i = 0;
    var result = [];
    while (i < fld.options.length) {
      opt = fld.options[i]
      optval = opt.value || opt.text
      if (opt.selected) {
        if (fld.type == 'select-one')
          return optval
        else
          result.push(optval)
      }
      i++
    }
    return result
  } else if (fld.type == 'checkbox' || fld.type == 'radio'){
    return fld.checked ? fld.value : ''
  } else {
    if (fld.type) {
      //alert('not handled yet: '+fld.type)
      //alert(d(fld))
      return fld.value
    } else {
      var result = []
      for (var i=0; i< fld.length; i++) {
        if (fld[i].checked) {
          result.push(fld[i].value)        
        }
      }
      return result
    }
  }
}
function escape_param(name, value) {
  if (value.map) {
    return value.map(function (v) { return escape_param(name, v) }).join('&')
  } else {
    return name + '=' + encodeURIComponent(value)
  }
}
DHTML = {};

DHTML.toggle_visibility_with_link = function(toggler_link, args)
{
  var display = args.display ? args.display : 'block';
  var class_name = args.class_name ? args.class_name : '';
  var ignore_list = args.ignore ? args.ignore : [];
  var root = args.root ? args.root : document;
  var showlabel = args.showlabel ? args.showlabel : 'show';
  var hidelabel = args.hidelabel ? args.hidelabel : 'hide';
  elems = getElementsByClassName(class_name, document);
  var onoff = 0;
  for (var i = 0; i < elems.length; i++)
  {
    var e = elems[i];
    if (ignore_list.contains(e.id))
      continue;
    if (e.style && e.style.display && (e.style.display==display || e.style.display == 'block'))
    {
      onoff = 0
      e.style.display = 'none';
    }
    else
    {
      onoff = 1
      try { e.style.display = display; }
      catch(err) { e.style.display = 'block'; }
    }
  }
  toggler_link.innerHTML = toggler_link.innerHTML==showlabel ? hidelabel : showlabel;
  if (args.cookie) {
    var cookie = args.cookie + '=' + onoff
    cookie += '; path=/'
    document.cookie = cookie
  }
}

DHTML.upd_visibility_with_link = function(toggler_link_id, args)
{
  if (args.cookie) {
    var cookiedef = args.cookiedef ? args.cookiedef : 0;
    var onoff = false;
    if (cookiedef) {
      onoff = document.cookie.indexOf(args.cookie+'=0') == -1;
    }
    else {
      onoff = document.cookie.indexOf(args.cookie+'=1') > -1;
    }
    DHTML.set_visibility_with_link(getElement(toggler_link_id), args, onoff);
  }
}

DHTML.set_visibility_with_link = function(toggler_link, args, onoff)
{
  var display = args.display ? args.display : 'block';
  var class_name = args.class_name ? args.class_name : '';
  var root = args.root ? args.root : document;
  var showlabel = args.showlabel ? args.showlabel : 'show';
  var hidelabel = args.hidelabel ? args.hidelabel : 'hide';
  elems = getElementsByClassName(class_name, document);
  for (var i = 0; i < elems.length; i++)
  {
    var e = elems[i];
    if (onoff)
    {
      try { e.style.display = display; }
      catch(err) { e.style.display = 'block'; }
    }
    else
    {
      e.style.display = 'none';
    }
  }
  toggler_link.innerHTML = onoff ? hidelabel : showlabel;
}

/* std_money_fmt 
 pass in a float, a currency symbol, 
 and 1/0 indicating if currency symbol is before the number
 returns formatted string, to two decimal places
*/
function std_money_fmt(m, dollarsign, before) {
  if (typeof(dollarsign)=="undefined") { dollarsign = '$'; }
  if (typeof(before)=="undefined") { before = 1; }
  var is_neg = (m < 0);
  m = Math.abs(m);
  // round to two places
  m = Math.round(100*m);
  var frac = m % 100;
  frac = ((frac < 10) ? '0' : '') + frac
  m = Math.floor(m / 100)
  var result = m+'.'+frac
  // add the currency symbol
  result = before ? dollarsign+result : result+dollarsign
  if (is_neg) {
    result = '-' + result;
  }
  return result
}

// make sure older browsers (IE 7 and down) have JSON support
var JSON;
if (JSON == undefined) {
  include('/hub/js/json2.js');
}

