// DTMaps desktop js
// Pixel Development, jonespm2@gmail.com

// var debugOn = false;
var ua = navigator.userAgent.toLowerCase();
var isExplorer   = ua.indexOf("msie")!=-1;
var genId = 100; // generated DOM ids
var marker_size;
// var marker_color;
var marker_max;
var map;
var showingControls = false;
var onMap = false;
var onMapControl = false;
var geocoder = null;
var getParam = new Array();
var chart = null; // used by Ajax
var markers = new Array();
var refreshTimer = null;
var inAutoRefresh = false;
var units;
var pendingSearchId = 0;
var haveSearchCenter = false;

var currMapTab = 'tab1';
var currAdminTab = 'description' // 'welcome';
var tab = new Array();
var inShowTab = false;
var fullResultsHeight = 90;

// called by ajax, after translate

function defineMapStart(zoom, lat, lng) {
  startZoom = zoom;
  startLat  = lat;
  startLng  = lng;
}

function parseQueryString() {
  var query = window.location.search.substring(1);
  var parms = query.split('&');
  for (var i=0; i<parms.length; i++) {
    var pos = parms[i].indexOf('=');
    if (pos > 0) {
      var key = parms[i].substring(0,pos);
      var val = parms[i].substring(pos+1);
      getParam[key] = val;
    }
  }
} 

function getCookieDef(name, value) {
  name = name.replace("_", "");
  if (document.cookie.length>0) {
    c_start = document.cookie.indexOf(name + "=");
    if (c_start!=-1) { 
      c_start=c_start + name.length+1; 
      c_end=document.cookie.indexOf(";",c_start);
      if (c_end==-1)
        c_end=document.cookie.length;
      return unescape(document.cookie.substring(c_start,c_end));
    } 
  }
  return value;
}

function getCookie(name) {
  return getCookieDef(name, '');
}

function setCookie(name,value) {
  domain = ".dtmaps.com"; 
  path   = "/";
  expiredays = 365;
  debug("setCookie name, value: " + name + ", " + value);
  name = name.replace("_", "");
  var exdate=new Date();
  exdate.setDate(exdate.getDate()+expiredays);
  document.cookie=name+ "=" +escape(value)+ ((expiredays==null) ? "" : ";expires="+exdate.toGMTString()) +
    ";path=" + path + ";domain=" + domain;
  debug("setCookie2: " + getCookie(name));
}

function getWindowHeight() {
  if (self.innerHeight) {
    windowHeight = self.innerHeight;
  } else if (document.documentElement && document.documentElement.clientHeight) {
    windowHeight = document.documentElement.clientHeight;
  } else if (document.body) {
    windowHeight = document.body.clientHeight;
  }
  return windowHeight;
}

function setObjHeight(id, height) {
  var obj;
  if (obj = $(id))
    obj.style.height = "" + height + "px"; 
}

function onResize() {
  var tabIndex = tab[currMapTab].tabIndex;
  var navHeight = 37;
  var aboveMapHeight = navHeight + 187;
  var footerHeight = 31;
  var shortResultsHeight = 90;
  var height = getWindowHeight();
  if (isExplorer)
    height -= 8;
  debug("getWindowHeight: " + height);
  // alert(height);
  var mapHeight = height - aboveMapHeight - footerHeight;
  var panelHeight = height - navHeight - footerHeight;
  debug("mapHeight: " + mapHeight);
  debug("panelHeight: " + panelHeight);
  if (true) { // panelHeight >= 600) { // otherwise every single tab must be resized
    setObjHeight("map", mapHeight);
    if ($("crosshairs")) {
      // $("crosshairs").style.top=((mapHeight-crosshairsSize)/2 - 39)+'px';
      $("crosshairs").style.top=(aboveMapHeight + (mapHeight-crosshairsSize)/2)+'px';
      var mapWidth = $("map").offsetWidth;
      debug("mapWidth: " + mapWidth);
      $("crosshairs").style.left=((mapWidth-crosshairsSize)/2)+'px'; // 
    }
    fullResultsHeight = mapHeight + shortResultsHeight;
    if (tab[currMapTab].showMap)
      $('div_results_' + tabIndex).style.height = "90px";
    else
      $('div_results_' + tabIndex).style.height = fullResultsHeight + "px";
    
    setObjHeight("side_panel", height-footerHeight);
    setObjHeight("inner_side_panel", height-footerHeight); 
    setObjHeight("side_panel_detail", panelHeight);
    // side_panel_detail = 564px
    // #admin_iframe, #help_iframe = 560px
    // welcome_iframe = 540px
    if (isExplorer)
      panelHeightCompensation = 24;
    else
      panelHeightCompensation = 4;
    setObjHeight("admin_iframe", panelHeight - panelHeightCompensation);
    setObjHeight("help_iframe", panelHeight - panelHeightCompensation);
    setObjHeight("welcome_iframe", panelHeight - panelHeightCompensation);
    // admin*, admin_iframe, help*, help_iframe, description, welcome_iframe, values, settings
    // admin_iframe, help_iframe, welcome_iframe, values, settings
    newCenter();    
  }
}

function roundCoord(i) {
  return i.toFixed(4);
}

function twoDigit(i) {
  if (i<10) 
    {i="0" + i}
  return i
}

function twoDecimals(n) {
  if (1 * n != 0) {
    n = Math.round(n * 100) / 100;
    if (Math.floor(n) == n)
      n = "" + n + ".00";
    else if (Math.floor(n * 10) == n * 10)
      n = "" + n + "0";
    if (1 * n >= 100)
      n = Math.round(n);
  }
  else
    n = "0.00";
  return n;
}

function time() {
  today = new Date();
  h = today.getHours();
  m = today.getMinutes();
  s = today.getSeconds();
  m = twoDigit(m);
  s = twoDigit(s);
  return h + ":" + m + ":" + s;
}

function trim(str) {
  return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
}

function degToRad(deg) {
  return Math.PI * deg/180;
}

function calcDistance(lat1, lon1, lat2, lon2) {
  if (lat1 == lat2 && lon1 == lon2)
    return 0;
  var radlat1 = degToRad(lat1);
  var radlat2 = degToRad(lat2);
  var radlon1 = degToRad(lon1);
  var radlon2 = degToRad(lon2);
  var theta   = lon1-lon2
  var radtheta = degToRad(theta)
  var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
  dist = Math.acos(dist);
  dist = dist * 180/Math.PI;
  dist = dist * 60 * 1.1515 * 1.609344; // km
  // if (unit=="K") { dist = dist * 1.609344 }
  // if (unit=="N") { dist = dist * 0.8684 } // nautical miles
  return dist;
}

function createUID() {
  genId++;
  return "UID_" + genId;
}

function $(id) {
  return document.getElementById(id);
}

function setVisibility(id, show) {
  debug("setVisibility: " + show);
  var obj;
  if (obj = $(id))
    obj.className = show ? "" : "hidden";
}

// values

function objVisible(id) {
  var obj;
  var visible = false;
  if (obj = $(id))
    visible = obj.className == "hidden";
  return visible;
}

function showObj(id) {
  setVisibility(id, true); 
}

function hideObj(id) {
  setVisibility(id, false); 
}

function setInnerHTMLIE(id, innerHTML) {
/******
* select_innerHTML - corrige o bug do InnerHTML em selects no IE
* Veja o problema em: http://support.microsoft.com/default.aspx?scid=kb;en-us;276228
* Versão: 2.1 - 04/09/2007
* Autor: Micox - Náiron José C. Guimarães - micoxjcg@yahoo.com.br
* @objeto(tipo HTMLobject): o select a ser alterado
* @innerHTML(tipo string): o novo valor do innerHTML
*******/
    obj = $(id);
    obj.innerHTML = ""
    var selTemp = document.createElement("micoxselect")
    var opt;
    selTemp.id="micoxselect1"
    document.body.appendChild(selTemp)
    selTemp = document.getElementById("micoxselect1")
    selTemp.style.display="none"
    if(innerHTML.toLowerCase().indexOf("<option")<0){//se não é option eu converto
        innerHTML = "<option>" + innerHTML + "</option>"
    }
    // innerHTML = innerHTML.toLowerCase().replace(/<option/g,"<span").replace(/<\/option/g,"</span")
    innerHTML = innerHTML.replace(/<option/g,"<span").replace(/<\/option/g,"</span")
    selTemp.innerHTML = innerHTML
      
    
    for(var i=0;i<selTemp.childNodes.length;i++){
  var spantemp = selTemp.childNodes[i];
  
        if(spantemp.tagName){     
            opt = document.createElement("OPTION")
    
   if(document.all){ //IE
    obj.add(opt)
   }else{
    obj.appendChild(opt)
   }       
    
   //getting attributes
   for(var j=0; j<spantemp.attributes.length ; j++){
    var attrName = spantemp.attributes[j].nodeName;
    var attrVal = spantemp.attributes[j].nodeValue;
    if(attrVal){
     try{
      opt.setAttribute(attrName,attrVal);
      opt.setAttributeNode(spantemp.attributes[j].cloneNode(true));
     }catch(e){}
    }
   }
   //getting styles
   if(spantemp.style){
    for(var y in spantemp.style){
     try{opt.style[y] = spantemp.style[y];}catch(e){}
    }
   }
   //value and text
   opt.value = spantemp.getAttribute("value")
   opt.text = spantemp.innerHTML
   //IE
   opt.selected = spantemp.getAttribute('selected');
   opt.className = spantemp.className;
  } 
 }    
 document.body.removeChild(selTemp)
 selTemp = null
}

function setInnerHTMLUNUSED(id, html) {
  debug("setInnerHTML: " + id + ", " + html);
  var a;
  if (a = $(id)) {
    if (!isExplorer || (id != 'group_id_1' && id != 'search_id_1'))
      a.innerHTML = html;
    else {
      setInnerHTMLIE(id, html);
    }
  }
}

function setInnerHTML(id, html) {
  // debug("setInnerHTML: " + id + ", " + html);
  var a;
  if (a = $(id)) {
    a.innerHTML = html;
    if (pendingSearchId > 0) { // ugly, but timing issues and structure make this the easiest way
      var tabIndex = tab[currMapTab].tabIndex;
      if (id == "parent_search_id_" + tabIndex) {
        debug("pendingSearchId: " + pendingSearchId);
        pendingSearchId = 1371;
        // setTimeout("setValue('search_id_' + tabIndex, pendingSearchId); pendingSearchId = 0;", 200);
        setValue('search_id_' + tabIndex, pendingSearchId);
        pendingSearchId = 0;
        performSearch(tabIndex);
      }
    }
  }
}

function getInnerHTML(id) {
  var a;
  if (a = $(id))
    return a.innerHTML;
  else
    return '';
}

function setChecked(id, on) {
  var a;
  if (a = $(id))
    a.checked = on;
}

function setValue(id, value) {
  var a, i, found;
  if (a = $(id)) {
    a.value = value;  // does not work for Opera in innerHTML dropdowns
/*    
    found = false;
    debug("setValue id, value: " + id + ", " + value);
    for (i = 0; i < a.length; i++)
      if (a.options[i].value == value) {
        debug("setValue found at: " + i);
        a.selectedIndex = i;
        found = true;
        break;
      }
    if (!found)
      a.selectedIndex = 0;
*/
  }
}

function getValue(id) {
  var a;
  if (a = $(id))
    return a.value;
  return '';
}

function getValueDef(id, defValue) {
  var a = getValue(id);
  if (a == '')
    a = defValue;
  return a;
}

function isReturn(e) {
  try {
    e = e | event;
  }
  catch (err) {}
  if (!e)
    e = window.event; // explorer, safari
  key = (e.which) ? e.which : e.keyCode;
  return key == 13;
}

function debug(html) {
  if (!debugOn)
    return;
  // return;
  html = html.replace(/</ig, "&lt;");
  html = html.replace(/>/ig, "&gt;");
  if (a = $('debug'))
    a.innerHTML = a.innerHTML + "<br>" + time() + " " + html;
}

function error(html) {
  alert(html);
  // setInnerHTML('message', html);
}

var noFocusObj = null;

function noFocus() {
  if (!noFocusObj)
    noFocusObj = $("nofocus");
  if (noFocusObj)
    noFocusObj.focus();
    // noFocusObj.blur();
}

function addDDItem(dropDownObj, item) {
  try {
    dropDownObj.add(item, null); // standards compliant
  }
  catch(e) {
    dropDownObj.add(item); // IE only
  }
}

// could be done through Ajax?

function getPhrase(id) {
  var b;
  if (b = $(id))
    return b.innerHTML;
  else
    return "";
}

// AJAX routine
  
function ajaxInnerOld(request, objId) {
  var xmlHttp;
  try
    {
    // Firefox, Opera 8.0+, Safari
    xmlHttp=new XMLHttpRequest();
    }
  catch (e)
    {
    // Internet Explorer
    try
      {
      xmlHttp=new ActiveXObject("Msxml2.XMLHTTP");
      }
    catch (e)
      {
      try
        {
        xmlHttp=new ActiveXObject("Microsoft.XMLHTTP");
        }
      catch (e)
        {
        alert("Your browser does not support AJAX!");
        return false;
        }
      }
    }
  xmlHttp.onreadystatechange=function()
    {
    if(xmlHttp.readyState==4)
      {
      debug("ajax objId, result: " + objId + ", " + xmlHttp.responseText);

      if (objId != '')
        setInnerHTML(objId, xmlHttp.responseText);
      else
        eval(xmlHttp.responseText);
      }
    }
  debug("ajax: " + request + ", " + objId);
  xmlHttp.open("GET", "ajax.asp?" + request, true);
  xmlHttp.send(null);
} // ajax




// AJAX class library that also supports Explorer 7
// http://www.unitedscripters.com/index.html?file=/scripts2/ajax1.html&ref=google%20en%20explorer%20responseText%20%20error%20firefox%20Search

function Ajax(echoFunction, responseType){//version 1.1.0
this.request=null;
this.response=null;
this.callingMethod='';
this.responseType=(typeof(responseType)=='string' && responseType.toLowerCase()=='responsexml ')?
  'responseXML':'responseText';
this.echoFunction=echoFunction||null;

/********* M E T H O D *********/
this.initialize=function(){
this.response=null;
this.callingMethod='';
if(!this.request){
  if(window['XMLHttpRequest']){/*IE7, Mozillas*/ try{this.request=new XMLHttpRequest();}catch(e){this.request=null;}; }
  else if(window['ActiveXObject']){/*IE<IE7*/
  var ajaxMSversions=[
    /*'Msxml2.DOMDocument.5.0', 'Msxml2.DOMDocument.4.0', 'Msxml2.DOMDocument.3.0', 'MSXML2.DOMDocument',*/ 'Msxml2.XMLHTTP', 'Microsoft.XMLHTTP'
  ];
    for(var v=0; v<ajaxMSversions.length; v++){
      try{this.request=new ActiveXObject(ajaxMSversions[v]); return this.request;}catch(e){this.request=null;};
    }
  }
  else if(window['createRequest']){ try{this.request=window.createRequest();}catch(e){this.request=null;}; }
  else{alert('XMLHTTP not enabled. Impossible to proceed.');}
};
return this.request;
}

/********* M E T H O D *********/
this.get=this.send=function(address, query, echoFunction, responseType){
if(!address || !this.initialize()){return false;};
this.callingMethod='GET';
this.responseType=responseType||this.responseType;
query=query||'';
query=query.replace(/\?/, '');
// query=unescape(query); // PMJ breaks things, like passing # as a parameter
debug("new ajax: " + address+'?'+query);
this.request.open('GET', (address+'?'+query), true);
this.request.setRequestHeader('Content-Type', 'text/xml');
if(typeof(echoFunction)!="function"){this.request.onreadystatechange=this.echo(this);/*currying*/}
else{this.request.onreadystatechange=echoFunction;};
this.request.send(null);
}

/********* M E T H O D *********/
this.post=function(address, send, echoFunction, responseType){
if(!address || !this.initialize()){return false;};
this.callingMethod='POST';
this.responseType=responseType||this.responseType;
send=send||'';
send=unescape(send);
this.request.open('POST', address, true);
this.request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
if(typeof(echoFunction)!="function"){this.request.onreadystatechange=this.echo(this);/*currying*/}
else{this.request.onreadystatechange=echoFunction;};
this.request.send(send);
}

/********* M E T H O D *********/
this.head=function(address, send, echoFunction){
if(!address || !this.initialize()){return false;};
this.callingMethod='HEAD';
send=send||'';
this.request.open('HEAD', address, true);
this.request.setRequestHeader('Content-Type', 'text/xml');
if(typeof(echoFunction)!="function"){this.request.onreadystatechange=this.echo(this);/*currying*/}
else{this.request.onreadystatechange=echoFunction;};
this.request.send((send||null));
}

/********* M E T H O D *********/
this.error=function(statusError){
if(statusError){
ajaxWorking--; ajaxShow(); // PMJ Addition
this.response=(this.request && this.request.status)? 'Ajax Error: '+this.request.status+': '+this.request.statusText: 'Ajax Error: Requested document may be temporarily unavailable';
// alert(this.response);
debug(this.response);
return this.response;
}
else{return false;};
}

/********* M E T H O D *********/
this.echo=function(ajaxInstance){
return function(){//currying
  if(ajaxInstance.request.readyState==4 || ajaxInstance.request.readyState=='complete'){
    if(ajaxInstance.request.status==200){
    ajaxInstance.response=
    (ajaxInstance.callingMethod=='GET' || ajaxInstance.callingMethod=='POST')?ajaxInstance.request[ajaxInstance.responseType]:
    (ajaxInstance.callingMethod=='HEAD')?ajaxInstance.request.getAllResponseHeaders():false;
      if(typeof(ajaxInstance.echoFunction)=="function"){
      return ajaxInstance.echoFunction(ajaxInstance.response);
      };/*no function passed as parameter: a default behaviour:*/
    return ajaxInstance.response;/*response is STORED in the instance.response property, ready for further manipulation*/
    }
    else{return ajaxInstance.error(1);};
  }
  else{return ajaxInstance.error(0);};
}//currying over
}
/*class ends - Keep this comment to reuse freely: http://www.unitedscripters.com/ */}


var ajaxWorking = 0;

function ajaxShow() {
  debug("ajaxShow: " + ajaxWorking);
  var ajaxClass = ajaxWorking > 0 ? "" : "hidden";
  $('ajax').className = ajaxClass;
}

function ajaxInner(request, objId, func) {
  var ajax1 = new Ajax(
    function(response){
      debug("ajax response: " + response.replace("<", "&lt;"));
      ajaxWorking--;
      ajaxShow();
      if (typeof objId == 'string' && objId != '')
        setInnerHTML(objId, response);
      else
        eval(response);
      if (typeof func == 'function') {
        func();
      }
    }
  );
  request += "&test_data=" + testData
  ajax1.get("ajax.asp", request);
  ajaxWorking++;
  ajaxShow();
} // ajaxInner

function junk(a) {
}





// translate all static text on site

function translateTagName(translateList, tag) {
  var a = document.getElementsByTagName(tag);
  for(i=0; i < a.length; i++){
    if (a[i].lang) {
      if (!a[i].id)
        a[i].id = createUID();
      translateList["" + a[i].id] = a[i].lang;
    }
  }
}

function performTranslations() {
  var translateList = new Object();
  var tags = new Array("h3", "h4", "span", "a", "button", "option", "p");
  for (var i = 0; i < tags.length; i++)
    translateTagName(translateList, tags[i]);
  var data = "";
  var delim = "";
  for (var id in translateList) {
    data += delim + id + "||" + translateList[id];
    delim = "|||";
  }
  if (data != '')
    ajaxInner("action=bulk_translate&data=" + data, "");
  else
    initPagePart2();
}

function changeLanguage() {
  debug("changeLanguage");
  setCookie("language", $("language").value);
  var newLoc = "desktop.asp?language=" + $("language").value;
  if (custom != "default")
    newLoc = newLoc + "&custom=" + custom
  document.location = newLoc;
  return;
}

function changeUnitsType() {
  units = $('units').value;
  setCookie("units", units);
  refresh();
}

function changeMarkerSize() {
  marker_size = $("marker_size").value;
  setCookie("marker_size", marker_size);
  refresh();
}

function changeMarkerColor() {
//  marker_color = $("marker_color").value;
//  setCookie("marker_color", marker_color);
//  refresh();
}

function toggleDisplay(triangleId, divId) {
  debug("toggleResultDisplay");
  var tabIndex = tab[currMapTab].tabIndex;
  triangleId += tabIndex;
  divId      += tabIndex;
  var triangle  = $(triangleId);
  var resultsObj = $(divId);
  if (resultsObj.className == '')
    triangle.src = "triangle.png";
  else
    triangle.src = "opentriangle.png";
  setVisibility(divId, resultsObj.className != '');
}

function toggleResultDisplay() {
  toggleDisplay("div_results_show_", "div_results_");
}
  
function toggleSearchDisplay() {
  toggleDisplay("div_search_show_", "div_search_");
}

function showHelp() {
  window.open ("http://www.dtmaps.com/docs/help.asp", "mywindow","menubar=0,resizable=1,scrollbars=1,width=420,height=650"); 
}

function addressFocus(index) {
  if (typeof(index) != 'number')
    index = tab[currMapTab].tabIndex;
  var id    = "address_" + index;
  debug("addressFocus index: " + index);
  if (getValue(id) == getPhrase('address_prompt')) {
    setValue(id, '');
    debug("addressFocus clearing");
  }
  $(id).className = '';
}

function addressBlur(index) {
  return;
  if (typeof(index) != 'number')
    index = tab[currMapTab].tabIndex;
  var id    = "address_" + index;
  debug("addressBlur index: " + index);
  if (getValue(id) == '') {
    setValue(id, getPhrase('address_prompt'));
    $(id).className = 'address_prompt';
    debug("addressBlur setting");
  }
}

function tabIndexToName(index) {
  for (var name in tab) {
    if (index == 1)
      return name;
    index--;
  }
  return null;
}

function setValueTabVisibility(groupId) {
  // setVisibility("ctrl_values", loggedOn && showSensor && groupShowSensor[groupId]);
  setVisibility("ctrl_values", loggedOn && groupShowSensor[groupId]);
}
    
// {buildSearch("search_id_" + index, getValueDef('group_id_' + index))};


function buildSearch(tabIndex) {
  debug("buildSearch: " + tabIndex);
  if (!isMerging(currMapTab)) {
    clearResults(currMapTab);
    hideValuesTab();
  }
  var tabName = tabIndexToName(tabIndex);
  debug("buildSearch tabIndex: " + tabIndex);
  var id = "parent_search_id_" + tabIndex;
  var groupId = getValueDef('group_id_' + tabIndex);
  
  if (false)
    ajaxInner("action=build_search_dd&group_id=" + groupId + "&index=" + tabIndex, id)
  else {
    // pre-loaded
    var searchDD = "<select id='search_id_"+tabIndex+"' name='search_id_"+tabIndex+"' onchange='performSearch("+tabIndex+")'>" + searches[groupId] + "</select>";
    setInnerHTML(id, searchDD);
  }
  
  // setValue("address_" + tabIndex, "");
  addressBlur(tabIndex);
  var showDates = false;
  try {
    if (groupHasDate[groupId])
      showDates = true;
  }
  catch (e) {}
  setVisibility("dates_" + tabIndex, showDates);
  setValueTabVisibility(groupId);
}

function performSearch(index, isRefresh) {
  var passedIndex = index;
  var workingMapTab = tabIndexToName(index); // might not be the currently shown one
  newPromo();
  // hideValuesTab();  // Oct 11, 2008
  debug("performSearch: " + index);
  searchId = getValueDef('search_id_' + index, -2);
  tab[workingMapTab].isolate = false;  // ???? maybe this isn't current tab - is this only on initialization?
  // var index = tab[currMapTab].tabIndex;
  var address = getValue("address_" + index);
  var firstChar = address.substr(0,1);

  if (firstChar == "$" || firstChar == "#" || firstChar == "@")
    if (searchId == -2)
      searchId = address;
      
  if (false) // firstChar == "$" || firstChar == "#" || firstChar == "@")
   showAddress(address);
  else {
    var zoom   = map.getZoom();
    var center = map.getCenter();
    var bounds = map.getBounds();
    if (workingMapTab != currMapTab) {
      // Nov 26, 2008 - last recorded map settings
      zoom = tab[workingMapTab].zoom;
      if (tab[workingMapTab].center)
        center = tab[workingMapTab].center;
      if (tab[workingMapTab].bounds) // we might not have a bounds - *** TODO
        bounds = tab[workingMapTab].bounds;
    }
    else {
      // Aug 19, 2008, don't clear address
      // setValue("address_" + index, ""); // wipe out the address as we are searching using dropdowns, not $node_id
      addressBlur(index);
    }
    var ne = bounds.getNorthEast();
    var sw = bounds.getSouthWest();
    var latMax = ne.lat();
    var latMin = sw.lat();
    var lonMax = ne.lng();
    var lonMin = sw.lng();
    var latCenter = center.lat();
    var lonCenter = center.lng();
    var isMerge   = $("merge_" + index).checked ? "1" : "0";
    
    var item = new Object();
    item.searchId = searchId;
    item.customIcon = tab[workingMapTab].customIcon;
    item.customIconOn = tab[workingMapTab].customIconOn;
    if (!isMerging(workingMapTab)) {
      tab[workingMapTab].searches = new Array();
      tab[workingMapTab].searches.push(item); // just a single item
    }
    else {
      var found = false;
      for (var i = 0; i < tab[workingMapTab].searches.length; i++)
        if (tab[workingMapTab].searches[i].searchId == searchId) {
          found = true;
          tab[workingMapTab].searches[i] = item;
        }
      if (!found)
        tab[workingMapTab].searches.push(item);
    }
    var delim = "";
    var searchList = "";
    for (var j = 0; j < tab[workingMapTab].searches.length; j++) {
      searchList += delim + tab[workingMapTab].searches[j].searchId;
      delim = ",";
    }
    
    if (false && isMerging(workingMapTab)) {
      latCenter = tab[workingMapTab].searchLat; // ********** IS IT UNDEFINED????? **************
      lonCenter = tab[workingMapTab].searchLon;
    }
    setSearchCenter(workingMapTab, latCenter, lonCenter);
    
    // note: searchList will need encoding for things like $p136:      
    //    var command = "action=node_search&param=" + encodeURIComponent(address); 
    debug("searchList: " + searchList);
    searchList = encodeURIComponent(searchList);
    
    var s = "action=perform_search&search_id=" + searchList + "&search_tab=" + tabIndexToName(passedIndex) + "&zoom=" + zoom + "&merge=" + isMerge;
    s += "&lat_center=" + latCenter + "&lon_center=" + lonCenter;
    s += "&lat_max=" + latMax + "&lon_max=" + lonMax;
    s += "&lat_min=" + latMin + "&lon_min=" + lonMin;
    if (groupHasDate[getValue("group_id_" + index)])
       s += "&start_date=" + getDateStr("date_" + index + "a", true) + "&end_date=" + getDateStr("date_" + index + "b", false);
    if (searchList != "-2") {
      debug("ajax: " + s);
      ajaxInner(s, "");
    }
    else {
      // probably need to clear out data **** TODO
      clearData(workingMapTab);
      finishedData();
    }
    if (!isRefresh) {
      tab[tabIndexToName(index)].currNodeId = null;
      setInnerHTML("desc_section_" + index, "");
    }
  }
}

// 'show' optional parameter (will toggle, otherise accepts passed setting)

function toggleMapDisplay(show) {
  if (typeof(show) != 'boolean')
    tab[currMapTab].showMap = !tab[currMapTab].showMap;
  else
    tab[currMapTab].showMap = show;
  var tabIndex = tab[currMapTab].tabIndex;
  
  if (tab[currMapTab].showMap) {
    $('div_results_' + tabIndex).style.height = "90px";
    $('map').style.display = 'block';
  }
  else {
    // $('div_results_' + tabIndex).style.height = "468px";
    $('div_results_' + tabIndex).style.height = fullResultsHeight + "px";
    $('map').style.display = 'none';
  }
}

function setSort(sortColumnIndex) {
  debug("setSort: " + sortColumnIndex);
  // .sort_title, .sortasc, .sortdesc, col4_title_1
  var tabIndex = tab[currMapTab].tabIndex;
  
  var currSort = tab[currMapTab].sortColumnIndex;
  if (currSort == sortColumnIndex) {
    debug("setSort - toggle order");
    tab[currMapTab].sortAsc = !tab[currMapTab].sortAsc;
    $('col' + currSort + '_title_' + tabIndex).className = tab[currMapTab].sortAsc ? 'sortasc' : 'sortdesc';
    changeResultSort();
  }
  else {
    $('col' + currSort + '_title_' + tabIndex).className = 'sort_title';
    tab[currMapTab].sortAsc = true;
    tab[currMapTab].sortColumnIndex = sortColumnIndex;
    $('col' + sortColumnIndex + '_title_' + tabIndex).className = tab[currMapTab].sortAsc ? 'sortasc' : 'sortdesc';
    changeResultSort();
  }
}

// navigation tab handling

function initTab(name, i) {
  var obj;
  debug("initTab name, i: " + name + ", " + i);
  var newTab = new Object();
  var tabIndex = 1 + i;
  var settings = null;
  try {
    if (tabIndex == 1) {
      debug("grab preset");
      settings = myList['presets']['default'];
      startLat  = 1 * settings.latitude;
      startLng  = 1 * settings.longitude;
      startZoom = 1 * settings.zoom;
      debug("grab preset, startZoom: " + startZoom);
    }
    else
      settings = myList['presets']['tab' + tabIndex];
  }
  catch (e) {}
  with (newTab) {
    newTab.isMap      = true;
    newTab.showMap    = true;
    newTab.icon       = null;
    newTab.tabName    = name; // WITH only works for accessing, not setting
    newTab.tabIndex   = tabIndex;
    newTab.zoom       = null;
    newTab.center     = null;
    newTab.mapType    = null;
    newTab.data       = null;
    newTab.currNodeId = "";
    newTab.currMarker = null;
    newTab.isolate    = false;
    newTab.searchLat  = null;
    newTab.searchLon  = null;
    newTab.searches   = null;
    
    // newTab.results    = new Array();
    newTab.currResultObjId = 0;
    newTab.sortColumnIndex = 4; // distance column
    newTab.sortAsc    = true;
    
    var group_id  = -2; // pull these from cookies (default preset, etc)
    var search_id = -2;
    
    if (settings) {
      newTab.zoom   = settings.zoom;
      newTab.center = new GLatLng(settings.latitude, settings.longitude);
      group_id      = settings.groupId;
      search_id     = settings.searchId;
      // ***************** NOTE - search muyst be performed AFTER map is created (need coords) *********************
    }
    
    // ajaxInner("action=build_group_dd&group_id="  + group_id + "&index=" + tabIndex, "parent_group_id_" + tabIndex);
    // ajaxInner("action=build_search_dd&group_id=" + group_id + "&search_id=" + search_id + "&index=" + tabIndex, "parent_search_id_" + tabIndex);

    $("ctrl_tab" + tabIndex).onclick = function() {showTab('tab' + tabIndex)};
    // $("div_search_show_" + tabIndex).onclick = function() {toggleSearchDisplay()};
    $("refresh_" + tabIndex).onclick = refresh;
    if ($("clear_" + tabIndex))
      $("clear_" + tabIndex).onclick = clearTab;
    if ($("zoom_to_markers_" + tabIndex)) {
      $("zoom_to_markers_" + tabIndex).onclick = zoomToMarkers;
      // $("zoom_to_origin_" + tabIndex).onclick = zoomToOrigin;
    }
 
    if ($("merge_" + tabIndex)) {
      $("merge_" + tabIndex).checked = false;
      $("merge_" + tabIndex).onclick = function() {if (!$("merge_" + tabIndex).checked) {clearData(); finishedData();}};
    }
    
    $("go_" + tabIndex).onclick = function() {showAddress(getValue('address_' + tabIndex))};
    $("address_" + tabIndex).onkeypress = function(event) {if (isReturn(event)) {showAddress(getValue('address_' + tabIndex)); return false;}};
    $("address_" + tabIndex).onfocus = addressFocus;
    $("address_" + tabIndex).onblur  = addressBlur;
    $("address_" + tabIndex).className = 'address_prompt';
    setValue("address_" + tabIndex, getPhrase('address_prompt'));
    // setValue("address_" + tabIndex, 'address.....');
    // $("div_results_show_" + tabIndex).onclick = function() {toggleResultDisplay()};
    if ($("sort_" + tabIndex))
      $("sort_" + tabIndex).onclick = changeResultSort;
    if ($("map_" + tabIndex))
      $("map_" + tabIndex).onclick = function() {toggleMapDisplay()};
    if ($("marker_select_" + tabIndex))
      $("marker_select_" + tabIndex).onclick = pickMarker;
      
    $("load_" + tabIndex).onclick   = runMyListItem;
    $("delete_" + tabIndex).onclick = deleteMyListItem;
    $("save_" + tabIndex).onclick   = saveMyListItem; // note that this will pass the event object, which is ignored
      
    for (var col = 2; col <= 4; col++)
      attachSort(tabIndex, col); // separate routine so that 'col' is 'remembered' within function
  }
  return newTab;
}

function attachSort(index, col) {
  if (obj = $('col' + col + '_title_' + index))
    obj.onclick = function() {setSort(col)}
}

function initTabs() {
  debug("initTabs");
  var tabNames = new Array("tab1", "tab2", "tab3", "tab4", "tab5");
  for (var i = 0; i < tabNames.length; i++) {
    name = tabNames[i];
    tab[name] = initTab(name, i);
  }
  var a;
  if (a = $("ctrl_" + currMapTab))
    a.className = "current_tab";
  if (a = $("ctrl_" + currAdminTab))
    a.className = "current_tab";
  $("ctrl_close").onclick = function() {hideObj('select_marker_dialog')};
  $("ctrl_default").onclick = function() {setIcon('')};
  debug("initTabs done");
}

// PMJ Jan 28, 2009 -1 means revert to pre google earth map type, 0 means stay the same but fix things up

function mapTypeHasChanged() {
  // return;
  var currMapType = map.getCurrentMapType();
  try {
    showGoogleEarth = currMapType == G_SATELLITE_3D_MAP;
  }
  catch(e) {
    showGoogleEarth = false;
  }
  if (showGoogleEarth) {
    for (i = 1; i <= 5; i++) // hide map controls - interferes with google earth
      hideObj("map_" + i);
    setIcon('', 0); // default
    hideObj('crosshairs');
  }
  else {
    for (i = 1; i <= 5; i++) // hide map controls - interferes with google earth
      showObj("map_" + i);
    showObj('crosshairs');
  }
}

function showTab(name) {
  inShowTab = true;
  toggleMapDisplay(true);
  var obj;
  noFocus();
  hideObj(currMapTab);
  if (currMapTab != '') {
    showObj("ctrl_" + currMapTab);
    hideObj("desc_section_" + tab[currMapTab].tabIndex);
    try {
      tab[currMapTab].zoom    = map.getZoom();
      tab[currMapTab].center  = map.getCenter();
      tab[currMapTab].mapType = map.getCurrentMapType();

      // Nov 26, 2008 refresh
      tab[currMapTab].bounds  = map.getBounds();
    }
    catch (e) {}
  }
  showObj(name);
  $("ctrl_" + name).className = 'current_tab';
  showObj("desc_section_" + tab[name].tabIndex);
  try {
    with (tab[name]) {
      if (zoom != null) {
        map.setCenter(center, zoom);
        map.setMapType(mapType);
        dtSetMapType(0);
      }
    }
    if (currMapTab != name) // changing to another map tab, so wipe out marker reference
      tab[currMapTab].currMarker = null;
    currMapTab = name;
  }
  catch (e) {}
  currMapTab = name;
  setValueTabVisibility(getValue("group_id_" + tab[currMapTab].tabIndex));
//  toggleMapDisplay(tab[currMapTab].showMap);
  finishedData();
  inShowTab = false;
}

function hideValuesTab() {
  if (currAdminTab == 'values')
    showAdminTab('description');
}

function updateIfValuesTab() {
  if (currAdminTab == 'values')
    showAdminTab('values');
}

function showAdminTab(name, reload) {
  debug("showAdminTab");
  var obj;
  noFocus();

  // Oct 11, 2008
  if (name == 'values')
    if (obj = $('ctrl_values'))
      if (obj.className == 'hidden')  
        return;
  
  if (obj = $(currAdminTab))
    obj.className = "hidden";
  if (currAdminTab != '')
    if (obj = $("ctrl_" + currAdminTab))
      obj.className = '';
  if (obj = $(name))
    obj.className = "";
  if (name == 'values') {
    if (chart) {
      chart.remove();
      chart = null;
    }
    setInnerHTML('my_chart', '');
    setInnerHTML('value_detail', '');
  }
  $("ctrl_" + name).className = 'current_tab';
  if (name == "help") {
    if (obj = $("help_iframe"))
      if (!obj.isLoaded || typeof reload != "undefined") {
        debug("showAdminTab - loading...");
        obj.isLoaded = true;
        obj.src = helpFile;
      }
  }
  else if (name == "admin") {
    if (obj = $("admin_iframe"))
      if (!obj.isLoaded || typeof reload != "undefined") {
        debug("showAdminTab - loading...");
        obj.isLoaded = true;
        obj.src = adminFile;
      }
  }
  else if (name == "values") {
    if (chart) {
      chart.remove();
      chart = null;
    }
    showNodeValues();
  }
  currAdminTab = name;
}

function changeResultSort() {
  sortMethod = 1 - sortMethod;
  finishedData();
  tab[currMapTab].currNodeId = -1;
  var tabIndex = tab[currMapTab].tabIndex;
  setInnerHTML("desc_section_" + tabIndex,"");
}

function pickMarker() {
  if ($('select_marker_present'))
    showObj('select_marker_dialog');
  else
    ajaxInner("action=select_marker", "select_marker_content", function() {showObj('select_marker_dialog')});
}

function changeMarkerMax() {
  marker_max = getValue('marker_max');
  setCookie('marker_max', marker_max);
  refresh();
}

// function changePageStyle() {
//  var style = getValue('page_style');
//  $('body').className = style;
  // also keep in cookie
//}

function performBackup() {
  if (trim(getValue("email")) == '')
    alert(getPhrase('email_required'));
  else
    ajaxInner("action=backup&email=" + getValue("email"),"");
}

// called from login/logout as we may have private groups and searches

function rebuildGroupAndSearchDD() {
  debug("rebuildGroupAndSearchDD");
  
  map.clearOverlays(); // wipe out all data (happens at login/logout)
  markers = new Array();
  for(var name in tab) {
    debug("rebuildGroupAndSearchDD tab: " + name);
    var tabIndex = tab[name].tabIndex;
    if (tab[name].isMap) {
      setInnerHTML('div_results_' + tabIndex, "<center>" + getPhrase('no_results') + "</center>"); // ''
      tab[name].data = new Array();

      var group_id  = -2; // pull these from existing dropdowns?
      var search_id = -2;
      ajaxInner("action=build_group_dd&group_id="  + group_id + "&index=" + tabIndex, "parent_group_id_" + tabIndex);
      ajaxInner("action=build_search_dd&group_id=" + group_id + "&search_id=" + search_id + "&index=" + tabIndex, "parent_search_id_" + tabIndex);
      // maybe also wipe out results??
    }
  }
  debug("rebuildGroupAndSearchDD done");
}

function performLogin() {
  ajaxInner("action=login&user_name=" + getValue("user_name") + "&password=" + getValue("password"), "", rebuildGroupAndSearchDD);
  setValue('user_name', '');
  setValue('password', '');
}

function performLogout() {
  ajaxInner("action=logout", "", rebuildGroupAndSearchDD);
}

function toggleDebug() {
  debugOn = !debugOn;
  $('debug').className = debugOn ? "" : "hidden";
  setInnerHTML('debug', '');
}

// add JS to the form controls (eg: change group dropdown regenerates the search dropdown)

function attachFormActions() {
  // we include the <select> tags so that the following will work right away.
  $("main_form").onsubmit    = function() {return false;}
  $("ctrl_description").onclick = function() {showAdminTab('description')};
  $("ctrl_values").onclick   = function() {showAdminTab('values')};
  $("ctrl_settings").onclick = function() {showAdminTab('settings')};
  $("ctrl_admin").onclick    = function() {showAdminTab('admin')};
  $("ctrl_help").onclick     = function() {showAdminTab('help')};

  $("ctrl_admin").ondblclick = function() {showAdminTab('admin', true)}; 
  $("ctrl_help").ondblclick  = function() {showAdminTab('help', true)};

  if ($("test_data")) {
    $("test_data").onclick    = function() {
      testData = $('test_data').checked ? 1 : 0;
      setCookie('test_data', testData);
      refresh();
    }
  }

  if ($("ctrl_debug"))
    $("ctrl_debug").onclick    = toggleDebug;
  $("language").onchange     = changeLanguage;
  $("marker_size").onchange  = changeMarkerSize;
//  $("marker_color").onchange = changeMarkerColor;
  $("marker_max").onchange   = changeMarkerMax;
//  $("page_style").onchange   = changePageStyle;
  $("units").onchange        = changeUnitsType;
  $("backup").onclick        = performBackup;
  $("login").onclick         = performLogin;
  $("logout").onclick        = performLogout;
  $("mobile_version").onclick = function() {document.location="index2.asp";}
}

function refreshTab(tabName) {
  var index = tab[tabName].tabIndex;
  debug("refreshTab index: " + tabName + ", " + index);
  performSearch(index, getValueDef('search_id_' + index, -2), true);
}

function refresh() {
  refreshTab(currMapTab);
}

function newCenter() {
  if (inShowTab) // timing issue, changing tabs will cause dist recalc to happen at wrong time
    return;
  if (!map) // page just starting up
    return;
  // take new center for dist origin
  debug("newCenter");
  var center = map.getCenter();
  var latCenter = center.lat();
  var lonCenter = center.lng(); 
  tab[currMapTab].searchLat = latCenter; 
  tab[currMapTab].searchLon = lonCenter;
  var tabIndex = tab[currMapTab].tabIndex; 
  // recalc all distances
  // and put in new distances into result list - can we do for tooltip?

  // tab[currMapTab].resultsHTML = ''; 
  if (tab[currMapTab].data)    
    for (i = 0; i < tab[currMapTab].data.length; i++) {
      with (tab[currMapTab].data[i]) {
        var dist = calcDistance(latCenter, lonCenter, latitude, longitude);
        var unitDisplay = getPhrase(units == 'metric' ? 'distance_km' : 'distance_mi');
        var unitMultiplier = units == 'metric' ? 1 : 1 / 1.6094;
        dist = "" + twoDecimals(unitMultiplier * dist) + " " + unitDisplay;
        // debug("newCenter " + id + " new dist: " + dist);
        setInnerHTML("dist_" + tabIndex + "_" + id, dist); // Does not work in Firefox, probably other browsers as well.
        // addResult(currMapTab, id, letter, title, latitude, longitude, dist, status, statusDesc);
      }
    }
  // finishResults(currMapTab);
}

function clearTab() {
  debug("clearTab");
  // wipe results list, markers, id tab, and internal data
  tab[currMapTab].currNodeId = "";
  tab[currMapTab].currMarker = null;
  // tab[currMapTab].data       = new Array();
  clearData();
  tab[currMapTab].searches   = new Array();
  var tabIndex = tab[currMapTab].tabIndex;
  setChecked("merge_" + tabIndex, false);
  setIcon('');
  finishedData();
  debug("clearTab end");
}

function autoRefresh() {
  // must set some sort of flag to prevent 'no results found' alert from coming up
  inAutoRefresh = currMapTab == 'tab1';
  refreshTab('tab1');
  inAutoRefresh = currMapTab == 'tab2';
  refreshTab('tab2');
  inAutoRefresh = currMapTab == 'tab3';
  refreshTab('tab3');
  inAutoRefresh = currMapTab == 'tab4';
  refreshTab('tab4');
  inAutoRefresh = currMapTab == 'tab5';
  refreshTab('tab5');
  inAutoRefresh = false;
}

function setRefresh(delay) {
  // alert("setRefresh delay: " + delay);
  if (refreshTimer) {
    clearInterval(refreshTimer);
    refreshTimer = null;
  }
  if (delay > 0)
    refreshTimer = setInterval("autoRefresh();", delay * 1000);
}

// Google Maps code

function showAddress(address) {
  var firstChar = address.substr(0,1);
  if (firstChar == "$" || firstChar == "#" || firstChar == "@") {
    // var searchParam = address.substr(1);
    tab[currMapTab].isolate = true;
    var index = tab[currMapTab].tabIndex;
    // tab[currMapTab].currNodeId = nodeId;
    setValue("group_id_" + index, -2);
    setValue("search_id_" + index, -2);
    setVisibility("dates_" + index, false);
    setSearchCenterIfUndefined(currMapTab);
    
    performSearch(tab[currMapTab].tabIndex, false);
    // var command = "action=node_search&param=" + encodeURIComponent(address);
    // debug(command);
    // ajaxInner(command, "");
  }
  else {
    // server-based gives us accuracy, which allows us to estimate zoom
    // ajaxInner("action=geocode&address=" + address + "&extra=refresh()", "");
    // return;
    if (geocoder) {

       geocoder.getLocations(address, function (result)
          { 
            if (result.Status.code == G_GEO_SUCCESS && result.Placemark.length > 0) {
              var accToZoom = new Array(1,5,7,9,11,13,15,16,17,17) 
              var location = result.Placemark[0];
              var accuracy = location.AddressDetails.Accuracy;
              var zoom     = accToZoom[accuracy];
              var coords   = location.Point.coordinates;
              var point    = new GLatLng(coords[1], coords[0]);
              map.setCenter(point, zoom);
              // performSearch(tab[currMapTab].tabIndex, false);
              newCenter(); // just recalc distances please
            }
            else
              alert(address + " not found");
          }
       );
    }
  }
}

function isolateNode(nodeId) {
  search = "$" + nodeId;
  var index = tab[currMapTab].tabIndex;
  setValue("address_" + index, search);
  showAddress(search);
}

function gotoNode(nodeId) {
  // just recenter on this node
  with (tab[currMapTab]) {
    for (var i = 0; i < data.length; i++)
      if (data[i].id == nodeId) {
        var item = data[i];
        map.setCenter(new GLatLng(item.latitude, item.longitude), map.getZoom());
        // setSearchCenter(item.latitude, item.longitude);
        newCenter();
        break;
      }
  }
  // refresh(); // cannot refresh as this does not work if merging $p136
  // finishedData();
}

function saveNode(nodeId) {
  saveMyListItem("objects", 1);
}

var createMarkerZIndex = 1;

function zIndexCalc(marker,b) {
  return createMarkerZIndex;
}

function lockNode(nodeId) {
  var obj;
  if (obj = $('lock_' + currMapTab + "_" + nodeId))
    for (var i = 0; i < tab[currMapTab].data.length; i++)
      if (tab[currMapTab].data[i].id == nodeId) {
        var locked = !(tab[currMapTab].data[i].locked == true);
        tab[currMapTab].data[i].locked = locked;
        obj.className = locked ? 'locked' : 'unlocked';
      }
}

function lockAllNodes() {
  var newState;
  if (tab[currMapTab].data)
    for (var i = 0; i < tab[currMapTab].data.length; i++)
      if (obj = $('lock_' + currMapTab + "_" + tab[currMapTab].data[i].id)) {
        if (i == 0)
          newState = !(tab[currMapTab].data[i].locked == true);
        tab[currMapTab].data[i].locked = newState;
        obj.className = newState ? 'locked' : 'unlocked';
      }
}


// brings a new marker to the front

function showNodeDetails(nodeId, isMarkerClick) {
  var tabIndex = tab[currMapTab].tabIndex;
  var valuesObjId = 'desc_section_' + tabIndex;
  if (true) { // *********** TODO - only the merged items should not work?
    if (tab[currMapTab].currMarker) {
      map.removeOverlay(tab[currMapTab].currMarker);
      delete tab[currMapTab].currMarker;
      tab[currMapTab].currMarker = null;
      // need to create each time if we want to have a label, custom labeled markers do not move properly
    }
    if (underMarker = markers[nodeId]) {
      createMarkerZIndex = 10000000; // on top
      tab[currMapTab].currNodeId = nodeId;
      with (underMarker) {
        if (tab[currMapTab].selIcon[status] == null)
          debug("showNodeDetails icon is null");
        var newIcon = tab[currMapTab].selIcon[status];
        // if (tab[currMapTab].customIcon)
        //   newIcon = tab[currMapTab].customIconOn;
        
        if (tab[currMapTab].searches[searchIdx])
          if (tab[currMapTab].searches[searchIdx].customIcon)
            newIcon = tab[currMapTab].searches[searchIdx].customIconOn;
              
        var currMarker = createMarker(newIcon, searchIdx, nodeId, underMarker.getLatLng(), myLetter, myTitle, myDistance, status);
      }
      map.addOverlay(currMarker);
      if (currMarker.setZIndexAdj)
        currMarker.setZIndexAdj(createMarkerZIndex+10000000);
      tab[currMapTab].currMarker = currMarker;
    }
    createMarkerZIndex = 1;
  }
  if (true) { // isMarkerClick) {
    // show the results
    selectResult(currMapTab, nodeId);
  }
  var show = (loggedOn || showSensor || groupShowSensor[getValue("group_id_" + tab[currMapTab].tabIndex)]) ? "1" : "0";
  // ajaxInner("action=node_details&node_id=" + nodeId + "&show_sensor=" + show + "&tab=" + currMapTab, valuesObjId); // show the details
  ajaxInner("action=node_details&node_id=" + nodeId + "&show_sensor=" + show + "&tab=" + currMapTab, valuesObjId, showNodeDetailsCallback); // show the details
}

function showNodeDetailsCallback() {
  // What we need to do is to get Ajax to assign the result to a variable,
  // so that eval does that, and then do inner ourselves and then set the 'show sensor tab'
  // ie: check and see if html has hidden value that says we can show the sensor values tab
  var showSensorObjId = 'show_sensor_' + currMapTab;
  // setTimeout("setVisibility('ctrl_values', getInnerHTML('" + showSensorObjId + "') == '1')", 100);
  // Oct 11, 2008 hide/show values tab, switch us over to the values tab if it's present
  setTimeout("setVisibility('ctrl_values', getInnerHTML('" + showSensorObjId + "') == '1'); showAdminTab('values');", 100);
}

function setIcon(fileName, size) {
  hideObj("select_marker_dialog");
  // alert("Now setting marker icon to: " + fileName);
  var obj, tabIndex;
  tabIndex = tab[currMapTab].tabIndex;
  if (obj = $("marker_select_" + tabIndex))
    if (fileName == '') {
      tab[currMapTab].customIcon = null;
      obj.src = "default_marker.png";
    }
    else {
      var filePath = "img/" + fileName;
      obj.src = filePath;
      var baseIcon = new GIcon(G_DEFAULT_ICON);
      baseIcon.iconSize = new GSize(size,size);
      baseIcon.shadowSize = new GSize(size,size);
      baseIcon.iconAnchor = new GPoint(Math.floor(size / 2),size);
      baseIcon.image  = filePath;
      baseIcon.shadow = null;
      
      var parts  = fileName.split(".");
      var fileNameOn = parts[0] + "_on." + parts[1]; 
      filePath = "img/" + fileNameOn;
      var baseIconOn = new GIcon(G_DEFAULT_ICON);
      baseIconOn.iconSize = new GSize(size,size);
      baseIconOn.shadowSize = new GSize(size,size);
      baseIconOn.iconAnchor = new GPoint(Math.floor(size / 2),size);
      baseIconOn.image  = filePath;
      baseIconOn.shadow = null;
      
      tab[currMapTab].customIcon   = baseIcon;
      tab[currMapTab].customIconOn = baseIconOn;
    }
}

function createIcon(size, color, makeSelected) {
//  debug("marker_color cookie: " + getCookie('marker_color'));
  var iconOptions = {};
  var labelVOffset = 0;
  var labelHOffset = 0;
  var iconSize;
  switch(size) {
    case "normal": iconSize = 32; labelVOffset = 33; labelHOffset = 6; break; // labelHOffset = 4
    case "mid":    iconSize = 24; labelVOffset = 28; labelHOffset = 3; break;
    case "small":  iconSize = 18; break;
    case "tiny":   iconSize = 12; break;
  }
  if (isExplorer)
    if (marker_size == "normal")
      labelVOffset -= 3;
    else
      labelVOffset -= 4;
  var primary, corner, stroke;
  switch(color) {         // #BE5950
    case "white":  primary = "#FFFFFFF";  selPrimary = "#CFCFCFFF"; selCorner = "#FFFFFFFF"; break;
    case "yellow": primary = "#FCF357FF"; selPrimary = "#BFB300FF"; selCorner = "#FFF88FFF"; break;
    // case "orange": primary = "#FFB100FF"; selPrimary = "#AF7B00FF"; selCorner = "#FFE39FFF"; break;
    case "orange": primary = "#FF7200FF"; selPrimary = "#BF5500FF"; selCorner = "#FFC18FFF"; break;
    case "red":    primary = "#FD776AFF"; selPrimary = "#DF1500FF"; selCorner = "#FFC5BFFF"; break;
    case "green":  primary = "#64BA4AFF"; selPrimary = "#228F00FF"; selCorner = "#B4DFA7FF"; break;

    case "blue":   primary = "#6BA7FEFF"; selPrimary = "#0000FFFF"; selCorner = "#9999FFFF"; break;

    default: primary = color; selPrimary = color; selCorner = color; break;
  }
  corner = primary;
  stroke = "#000000FF";
  iconOptions.width  = Math.floor(iconSize / 2 + 1);
  iconOptions.height = iconSize;
  iconOptions.primaryColor = primary;
  iconOptions.cornerColor  = corner;
  iconOptions.strokeColor  = stroke;
  var results = new Object();
  var icon = MapIconMaker.createMarkerIcon(iconOptions);
  icon.labelVOffset = labelVOffset;
  icon.labelHOffset = labelHOffset;
  results.icon = icon;
  if (makeSelected) {
    iconOptions.primaryColor = selPrimary;
    iconOptions.cornerColor  = selCorner;
    var selIcon = MapIconMaker.createMarkerIcon(iconOptions);
    selIcon.labelVOffset = labelVOffset;
    selIcon.labelHOffset = labelHOffset;
    results.selIcon = selIcon;
  }
  return results;
}

function createMarker(icon, searchIdx, id, point, letter, title, distance, status) {
  // var marker = new GMarker(point, {title: markerTitle});
  if (icon == null)
    debug("createMarker icon is null");
  var labelText = "";
  var extra = "";
  extra = 2;
  if (letter == "W")
    extra = 0;
  if (letter == "G" || letter == "Q" || letter == "M")
    extra = 1;
  extra = " padding-left: " + extra + "px;";
  if (marker_size == "mid")
    labelText = "<span style='font-family: verdana; font-size: 9px;" + extra + "'>"+letter+"</span>";
  if (marker_size == "normal")
    labelText = "<span style='font-family: verdana; font-weight: bold; font-size: 12px;" + extra + "'>"+letter+"</span>";
  var opts = { 
    "icon":        icon,
    "title":       letter + ". " + title + " (" + distance + ")",
    "labelText":   labelText,      // you can use HTML inside the {labelText}, or you can style it with {labelClass}
    "labelOffset": new GSize(-icon.labelHOffset, -icon.labelVOffset) // 32
  };
  if (createMarkerZIndex != 1) {
    // for the special 'on top' marker, indicating the current marker
    opts["zIndexProcess"] = zIndexCalc;
  }
  var marker;
  // if (tab[currMapTab].customIcon)
  if (tab[currMapTab].searches &&  searchIdx < tab[currMapTab].searches.length && tab[currMapTab].searches[searchIdx].customIcon) {
    debug("GMarker");
    marker = new GMarker(point, opts);
  }
  else {
    opts.title = opts.title.replace("&#39;", "'"); // jan 28 2009 fix tooltip problem 
    debug("LabeledMarker title, label, labelOffset: " + opts.title + ", " + opts.labelText + ", " + opts.labelOffset.width + ", " + opts.labelOffset.height);
    marker = new LabeledMarker(point, opts);
  }
  marker.searchIdx  = searchIdx;
  marker.id         = id;
  marker.myLetter   = letter;
  marker.myTitle    = title;
  marker.myDistance = distance;
  marker.status     = status;
  GEvent.addListener(marker, "click", function() {
    showNodeDetails(id, true);
  });
  return marker;
}

function addMarker(icon, searchIdx, id, point, label, title, distance, status) {
  debug("addMarker searchIdx: " + searchIdx);
  if (icon == null)
    debug("addMarker icon is null");
//  if (tab[currMapTab].customIcon)
//    icon = tab[currMapTab].customIcon; // *********** will need to adjust so that only merge extras get this
  try {
    if (tab[currMapTab].searches[searchIdx])
      if (tab[currMapTab].searches[searchIdx].customIcon)
        icon = tab[currMapTab].searches[searchIdx].customIcon;
  }
  catch(e) {}
  var marker = createMarker(icon, searchIdx, id, point, label, title, distance, status);
  map.addOverlay(marker);
  debug("addMarker end");
  return marker;
}

function addSearchOriginMarker(point) {
  var icons = createIcon("tiny", "#006FFF", false); // #006FFF #6BA8FE #FBCB07 #FB8B07
  var opts = { 
    "icon":        icons.icon,
    "title":       "",
    "labelText":   "",      // you can use HTML inside the {labelText}, or you can style it with {labelClass}
    "labelOffset": new GSize(0, 0)
  };
  var marker = new GMarker(point, opts); // center is not labeled
  map.addOverlay(marker);
}

function sign(a) {
  return a < 0 ? -1 : 1;
}

// map functions

function getZoomCtrlValue() {
  // map.setZoom(getValue('zoom_dd'));
  map.setCenter(map.getCenter(), 1 * getValue('zoom_dd'));
  // alert('getZoomCtrlValue:' + getValue('zoom_dd'));
  return getValue('zoom_dd');
}

function setBtnStyle(button) {
  button.style.textDecoration = "none";
  button.style.color = "black"; // "#0000cc";
  button.style.backgroundColor = "white";
  button.style.font = "small Arial";
  button.style.fontSize = "12px";
  button.style.border = "1px solid black";
  button.style.padding = "2px";
  button.style.paddingLeft = "6px";
  button.style.paddingRight = "6px";
  button.style.marginBottom = "3px";
  button.style.textAlign = "center";
//  button.style.width = "6em";
  button.style.cursor = "pointer";
}

// crosshairs for Map http://www.daftlogic.com/sandbox-google-maps-centre-crosshairs.htm

var crosshairsSize = 19;

GMap2.prototype.addCrosshairs = function() {
  var container=this.getContainer();
  if(this.crosshairs)
    this.removeCrosshairs();
  var crosshairs=document.createElement("img");
  crosshairs.id = "crosshairs";
  crosshairs.ondblclick=function() {map.zoomIn();}
  crosshairs.src='crosshairs.gif';
  crosshairs.style.width=crosshairsSize+'px';
  crosshairs.style.height=crosshairsSize+'px';
  crosshairs.style.border='0';
  crosshairs.style.position='relative';
  // crosshairs.style.top=((container.clientHeight-crosshairsSize)/2)+'px';
  crosshairs.style.top=((container.clientHeight-crosshairsSize)/2 - 39)+'px'; // clientHeight lies, varies across browsers (due to custom controls?)
  crosshairs.style.left="0px"; // The map is centered so 0 will do
  crosshairs.style.zIndex='500';
  container.appendChild(crosshairs);
  this.crosshairs=crosshairs;
  return crosshairs;
}

// http://www.breakingpar.com/bkp/home.nsf/0/87256B280015193F87256CFB006C45F7

function checkTimeZone() {
   var rightNow = new Date();
   var date1 = new Date(rightNow.getFullYear(), 0, 1, 0, 0, 0, 0);
   var date2 = new Date(rightNow.getFullYear(), 6, 1, 0, 0, 0, 0);
   var temp = date1.toGMTString();
   var date3 = new Date(temp.substring(0, temp.lastIndexOf(" ")-1));
   var temp = date2.toGMTString();
   var date4 = new Date(temp.substring(0, temp.lastIndexOf(" ")-1));
   var hoursDiffStdTime = (date1 - date3) / (1000 * 60 * 60);
   var hoursDiffDaylightTime = (date2 - date4) / (1000 * 60 * 60);
   if (false)
     if (hoursDiffDaylightTime == hoursDiffStdTime) {
        alert("Time zone is GMT " + hoursDiffStdTime + ".\nDaylight Saving Time is NOT observed here.");
     } else {
        alert("Time zone is GMT " + hoursDiffStdTime + ".\nDaylight Saving Time is observed here.");
     }
}

// time control, shows time estimate given longitude on map

function updateClock() {
  // debug("updateClock");
  var timeZone = 0;
  try {
    var point = map.getCenter();
    // debug("updateClock 1");
    var longitude = point.lng();
    var absLongitude = Math.abs(longitude);
    // debug("updateClock 2 " + longitude);
    timeZone = sign(longitude) * Math.floor((absLongitude + 7.5) / 15);
    // debug("updateClock longitude, timeZone: " + longitude + ", " + timeZone);
  }
  catch (e) {
  }
  var curdate = new Date()
  var gmtHours = curdate.getUTCHours()
  var gmtMinutes = curdate.getUTCMinutes()
  var hoursOffsetGMT = -curdate.getTimezoneOffset() / 60;
  var minutesOffsetGMT = -curdate.getTimezoneOffset();
  var mapHours = (gmtHours + timeZone + 24) % 24;
  var mapMinutes = gmtMinutes;
  var obj = $('clock_control');
  if (timeZone >= 0)
    timeZone = "+" + timeZone;
  obj.innerHTML = twoDigit(mapHours) + ":" + twoDigit(mapMinutes) + " GMT" + timeZone;
}

function TimeControl() {
}

TimeControl.prototype = new GControl();

TimeControl.prototype.initialize = function(map) {
  var container = document.createElement("div");
  
  var clockDiv = document.createElement("div");
  this.setButtonStyle_(clockDiv);
  clockDiv.id = "clock_control";
  clockDiv.style.width = "120px";
  clockDiv.style.paddingLeft = "0";
  clockDiv.style.paddingRight = "0";
  clockDiv.title = titleTime;

  container.appendChild(clockDiv);
  var theTimeDiv = document.createTextNode("");
  clockDiv.appendChild(theTimeDiv);
  
  map.getContainer().appendChild(container);
  updateClock();

  GEvent.addListener(map, "moveend", updateClock);
  GEvent.addListener(map, "resize", updateClock);
  GEvent.addListener(map, "move", updateClock);

  return container;
}

TimeControl.prototype.getDefaultPosition = function() {
  return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(7, 34));
  // return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(7, 7));
}

TimeControl.prototype.setButtonStyle_ = function(button) {setBtnStyle(button)}

// location control, shows latitude and longitude of the map's center

function updateLocation() {
  // debug("updateLocation");
  try {
    var point = map.getCenter();
    var lat = point.lat();
    var lng = point.lng();
    var obj = $('location_control');
    obj.innerHTML = roundCoord(lat) + ", " + roundCoord(lng);
  }
  catch (e) {
  }
}

// latitude/longitude display

function LocationControl() {
}

LocationControl.prototype = new GControl();

LocationControl.prototype.initialize = function(map) {
  var container = document.createElement("div");
  
  var locationDiv = document.createElement("div");
  this.setButtonStyle_(locationDiv);
  locationDiv.id = "location_control";
  locationDiv.style.width = "120px";
  locationDiv.style.paddingLeft = "0";
  locationDiv.style.paddingRight = "0";
  locationDiv.title = titleLatLong;
  container.appendChild(locationDiv);
  var latLongDiv = document.createTextNode("5, 5");
  locationDiv.appendChild(latLongDiv);
  map.getContainer().appendChild(container);
  updateLocation();

  GEvent.addListener(map, "moveend", updateLocation);
  GEvent.addListener(map, "resize", updateLocation);
  GEvent.addListener(map, "move", updateLocation);

  return container;
}

LocationControl.prototype.getDefaultPosition = function() {
  // return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(102, 34));
  // return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(102, 7));
  return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(7, 7));
}

LocationControl.prototype.setButtonStyle_ = function(button) {setBtnStyle(button)}

// map type control

function setMapTypeCtrl() {
  var mapType = G_SATELLITE_MAP;
  switch (getValue('maptype_dd')) {
    case 'map':       mapType = G_NORMAL_MAP;        break;
    case 'satellite': mapType = G_SATELLITE_MAP;     break;
    case 'hybrid':    mapType = G_HYBRID_MAP;        break;
    case 'terrain':   mapType = G_PHYSICAL_MAP;      break;
    case 'earth':     try { mapType = G_SATELLITE_3D_MAP } catch(e) {};  break;
  }
  map.setMapType(mapType);
  noFocus();
}

function MapTypeControl() {
}

MapTypeControl.prototype = new GControl();

MapTypeControl.prototype.initialize = function(map) {
  var container = document.createElement("div");
  
  var innerDiv = document.createElement("div");
  this.setButtonStyle_(innerDiv);
  container.appendChild(innerDiv);
  var content = document.createElement("div");
  content.innerHTML = mapTypeCtrl;
  innerDiv.id = "maptype_control";
  innerDiv.appendChild(content);
  
  map.getContainer().appendChild(container);
  updateLocation();
  return container;
}

MapTypeControl.prototype.getDefaultPosition = function() {
  return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 38));
}

MapTypeControl.prototype.setButtonStyle_ = function(button) {setBtnStyle(button)}



// zoom control

function updateZoom() {
  // alert("updateZoom to: " + map.getZoom());
  setValue('zoom_dd', map.getZoom());
}

function setZoomCtrl() {
  map.setZoom(1*getValue('zoom_dd'));
  setValue('zoom_dd', map.getZoom()); // in case we tried to go too far
  noFocus();
}

function ZoomControl() {
}

ZoomControl.prototype = new GControl();

ZoomControl.prototype.initialize = function(map) {
  var container = document.createElement("div");
  
  var innerDiv = document.createElement("div");
  this.setButtonStyle_(innerDiv);
  container.appendChild(innerDiv);
  var content = document.createElement("div");
  content.innerHTML = '<img src="btn_zoom_to_markers.gif" class="button" id="zoom_to_markers" onclick="zoomToMarkers();" title="' + titleZoomToMarkers + '"/>' + zoomCtrl;
  // <img src="btn_zoom_to_origin.gif" class="button" id="zoom_to_origin" onclick="zoomToOrigin();" title="' + titleZoomToOrigin + '"/>' + zoomCtrl;
  innerDiv.id = "zoom_control";
  innerDiv.appendChild(content);
  
  map.getContainer().appendChild(container);
  GEvent.addListener(map, "zoomend", updateZoom);
  updateZoom();
  return container;
}

ZoomControl.prototype.getDefaultPosition = function() {
  // return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 7)); // jan 28, 2009
  return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 40));
}

ZoomControl.prototype.setButtonStyle_ = function(button) {setBtnStyle(button)}


function initMap() {
  debug("initMap, startZoom: " + startZoom);
  if (GBrowserIsCompatible()) { 
    map = new GMap2(document.getElementById("map"));
    try {
      map.enableScrollWheelZoom();
    }
    catch(e) {}
//    map.addControl(new GLargeMapControl());
//    map.addControl(new GMapTypeControl());
    // map.addCrosshairs();
    try {
      map.addMapType(G_PHYSICAL_MAP);
      map.addMapType(G_SATELLITE_3D_MAP); // jan 28, 2009
    }
    catch(e) {}
    map.setCenter(new GLatLng(startLat,startLng),startZoom);
    map.addControl(new GScaleControl());
    
    // Jan 28, 2009
    // map.addControl(new MapTypeControl());
    try {
      map.addControl(new GHierarchicalMapTypeControl());
    }
    catch(e) {
      map.addControl(new GMapTypeControl());
    }
    GEvent.addListener (map, "maptypechanged", mapTypeHasChanged);
    
    map.addControl(new ZoomControl());
    map.addControl(new TimeControl());
    map.addControl(new LocationControl());
    var overView = new GOverviewMapControl(new GSize(150,150));
    map.addControl(overView);
    overView.hide(true);
    map.checkResize(); // maybe fix for initially hidden overview     

    // only show controls when mouse is over map, but just works for Firefox in dropdowns    
    map.hideControls();
    // GEvent.addListener(map, "mouseover", function(){map.showControls()});
    // GEvent.addListener(map, "mouseout", function() {map.hideControls()});

    GEvent.addListener(map, "moveend", newCenter);

    // Explorer and Safari fire these functions off even if we are on a drodown within the map
    GEvent.addListener(map, "mouseover", function(){onMap = true});
    GEvent.addListener(map, "mouseout", function() {onMap = false});
    var obj;
    if (obj = $("zoom_dd")) {
      obj.onfocus = function() {onMapControl = true}
      obj.onblur  = function() {onMapControl = false}
    }
    if (obj = $("maptype_dd")) {
      obj.onfocus = function() {onMapControl = true}
      obj.onblur  = function() {onMapControl = false}
    }
    setInterval("handleMapControls();", 50);
    
    geocoder = new GClientGeocoder();
  }
  else {
    alert(getPhrase('no_google_maps'));
  }
}

function handleMapControls() {
  var show = onMap || onMapControl;
  if (show != showingControls) {
    showingControls = show;
    if (show)
      map.showControls();
    else
      map.hideControls()    
  }
}


function zoomToOrigin() {
  zoomToMarkers(true);
}


function zoomToMarkers(useOrigin) {
  if (typeof(useOrigin) != 'boolean')
    useOrigin = false;
  debug("zoomToMarkers");
  var slopPercentage = 5;
  var heightOffsetPct = 5;
  var count = 0;
  var thePoint, x, y, minX, maxX, minY, maxY, span;
  for (var nodeId in markers)
  {
    debug("zoomToMarkers 2");
    marker = markers[nodeId];
    if (true)
    {
      thePoint = marker.getPoint();
      // x = thePoint.x; y = thePoint.y;
      x = thePoint.lat(); y = thePoint.lng();
      if (count == 0)
      {
        minX = x; maxX = x; minY = y; maxY = y;
      }
      else
      {
        if (x < minX) minX = x;
        if (x > maxX) maxX = x;
        if (y < minY) minY = y;
        if (y > maxY) maxY = y;
      }
      count++;
    }
  }
  if (useOrigin) {
    var centerLat = tab[currMapTab].searchLat;
    var centerLon = tab[currMapTab].searchLon;
    x = centerLat;
    y = centerLon;
    xDiff = Math.max(Math.abs(minX - x), Math.abs(maxX - x));
    yDiff = Math.max(Math.abs(minY - y), Math.abs(maxY - y));
    minX = centerLat - xDiff;
    maxX = centerLat + xDiff;
    minY = centerLon - yDiff;
    maxY = centerLon + yDiff;
  }
  if (count == 1)
    map.setCenter(new GLatLng(x,y), map.getZoom());
  else if (count > 1)
  {
    debug("zoomToMarkers 3");
    var center = new GLatLng((minX + maxX) / 2, (minY + maxY) / 2)
    span = new GSize(Math.abs(maxX - minX), Math.abs(maxY - minY));
    slopWid = 0;
    slopHgt = 0;
    if (typeof slopPercentage != "undefined")
    {
      debug("zoomToMarkers 4");
      slopWid = span.width * slopPercentage / 200;
      slopHgt = span.height * slopPercentage / 200;
      span.width  *= 1 + slopPercentage / 100;
      span.height *= 1 + slopPercentage / 100;
    }
    deltaHgt = 0;
    if (typeof heightOffsetPct != "undefined")
    {
      debug("zoomToMarkers 5");
      deltaHgt = span.height * heightOffsetPct / 100;
      center = new GLatLng(center.lat() + deltaHgt, center.lng());
    }
    // needs slop
    debug("zoomToMarkers 6");
    var bounds = new GLatLngBounds(new GLatLng(minX-slopHgt, minY-slopWid), new GLatLng(maxX+slopHgt, maxY+slopWid)); // sw, ne
    var zoom = map.getBoundsZoomLevel(bounds);
    debug("zoomToMarkers 7");
    if (useOrigin)
      center = new GLatLng(centerLat, centerLon);
    map.setCenter(center, zoom);
    debug("zoomToMarkers 8");
  }
}

function isMerging(tabName) {
  var tabIndex = tab[tabName].tabIndex;
  var obj;
  if (obj = $("merge_" + tabIndex))
    return obj.checked && (tab[tabName].searchLat != null) && (tab[tabName].data.length > 0);
  else
    return false;
}

// following routines called by Ajax

function clearData(tabName) {
  if (typeof tabName == "undefined")
    tabName = currMapTab;
  // tab[currMapTab].data = new Array();
  var temp = new Array();
  if (tab[tabName].data)
    for (var i = 0; i < tab[tabName].data.length; i++)
      if (obj = $('lock_' + tabName + "_" + tab[tabName].data[i].id))
        if (obj.className == 'locked') {
          tab[tabName].data[i].searchIdx = 0;
          temp.push(tab[tabName].data[i]);
        }
  tab[tabName].data = temp;
}

function setSearchCenter(tabName, latitude, longitude) {  // currMapTab
  haveSearchCenter = true;
  tab[tabName].searchLat = latitude;
  tab[tabName].searchLon = longitude;
}

function setSearchCenterIfUndefined(tabName) {
  if (!haveSearchCenter) {
    var center    = map.getCenter();
    var latCenter = center.lat();
    var lonCenter = center.lng();
    setSearchCenter(tabName, latCenter, lonCenter);
  }
}

function setData(tabName, nodeId, latitude, longitude, title, status, statusDesc, distance) {
  if (tabName == 0)
    tabName = currMapTab; // node search, etc
  var searchIdx = tab[tabName].tabIndex;
  var obj = new Object();
  var index = tab[tabName].data.length;
  if (status >= markerColors.length)
    status = markerColors.length - 1;
  else if (status < 0)
    status = 0;
  obj.locked     = false;
  obj.index      = index; // adjust below if already present??  TODO - check
  obj.searchIdx  = searchIdx;
  obj.id         = nodeId;
  obj.latitude   = latitude;
  obj.longitude  = longitude;
  obj.title      = title;
  obj.status     = status;
  obj.statusDesc = statusDesc;
  obj.distance   = distance;
  var found = false;
  for (var i = 0; i < tab[tabName].data.length; i++)
    if (tab[tabName].data[i].id == nodeId) {
      found = true;
      var temp = tab[tabName].data[i].locked;
      tab[tabName].data[i] = obj;
      tab[tabName].data[i].locked = temp;
      break;
    }
  if (!found)
    tab[tabName].data.push(obj);
}

function sortCompare(a,b) {
  if ( a < b )
    return -1;
  if ( a > b )
    return 1;
  return 0; // a == b
}

var sortMethod = 1; // maybe into tab data

// called when you switch map tabs, or change settings

function finishedData(workingTab) {
  debug("finishedData");
  var i, icons, color;
  var currFound = false;

  if (typeof workingTab == "undefined" || workingTab == "undefined") // Oct 28, 2008
    workingTab = currMapTab;

  try {
    var unused = workingTab + "a";
  }
  catch (e) {
    workingTab = currMapTab;
  }
    
  try {
    var tabIndex = tab[workingTab].tabIndex;
    debug("currMapTab1: " + currMapTab + ", workingTab: " + workingTab);
  }
  catch (e) {
    debug("currMapTab2: " + currMapTab + ", workingTab: " + workingTab);
  }
  tab[workingTab].resultsHTML = '';
  if (workingTab == currMapTab) {
    // Nov 26, 2008 - are we looking at it?
    map.clearOverlays();
    markers = new Array();
    tab[workingTab].icon    = new Array();
    tab[workingTab].selIcon = new Array();
    for (i = 0; i < markerColors.length; i++) {
      color = markerColors[i];
      icons = createIcon(marker_size, color, true);
      tab[workingTab].icon[i]    = icons.icon;
      tab[workingTab].selIcon[i] = icons.selIcon;
    }
  }
  if (tab[workingTab].data) {
    with (tab[workingTab]) {
      switch(sortColumnIndex) {
        case 4:
          if (sortAsc)
            tab[workingTab].data.sort(function(a,b){return a.distance - b.distance}) 
          else
            tab[workingTab].data.reverse(function(a,b){return a.distance - b.distance}) 
          break;
        case 3:
          if (sortAsc)
            tab[workingTab].data.sort(function(a,b){var c = a.status - b.status; if (c == 0) c = a.index - b.index; return c}) 
          else
            tab[workingTab].data.reverse(function(a,b){var c = a.status - b.status; if (c == 0) c = a.index - b.index; return c})
          break;
        case 2:
          if (sortAsc)
            tab[workingTab].data.sort(function(a,b){return sortCompare(a.title.toLowerCase(),b.title.toLowerCase())})
          else
            tab[workingTab].data.reverse(function(a,b){return sortCompare(a.title.toLowerCase(),b.title.toLowerCase())})
          break;
      } // switch
    } // with
    // after sorting, clicking on a result pops the result to elsewhere on the list - problem!
  
    debug("finishedData marker_max: " + marker_max + " data.length: " + tab[workingTab].data.length);
    for (i = 0; i < tab[workingTab].data.length; i++) {
      with (tab[workingTab].data[i]) {
        var dist = calcDistance(tab[workingTab].searchLat, tab[workingTab].searchLon, latitude, longitude);
        var unitDisplay = getPhrase(units == 'metric' ? 'distance_km' : 'distance_mi');
        if (true) { // distance != 'n/a'
          var unitMultiplier = units == 'metric' ? 1 : 1 / 1.6094;
          dist = "" + twoDecimals(unitMultiplier * dist) + " " + unitDisplay;
        }
        var letter = '';
        if (tab[workingTab].currNodeId == id)
          currFound = true;
        if (i < marker_max) { // 26
          // if (tab[workingTab].currNodeId == id)
          //  currFound = true;
          letter = String.fromCharCode(i + 65);
          tab[workingTab].data[i].letter = letter;
          debug("finishedData marker " + id + " status: " + status + " searchIdx: " + searchIdx);
          if (tab[workingTab].icon[status] == null)
            debug("finishedData icon is null");
          if (workingTab == currMapTab) // Nov 26, 2008 - are we looking at this?
            markers["" + id] = addMarker(tab[workingTab].icon[status], searchIdx, id, new GLatLng(latitude, longitude), letter, title, dist, status);
        }
        else
          tab[workingTab].data[i].letter = '';
        addResult(workingTab, id, letter, title, latitude, longitude, dist, status, statusDesc, locked);
      }
    }
    finishResults(workingTab);
    if (workingTab == currMapTab) { // Nov 26, 2008 - are we looking at this?
      if (currFound) {
        hideObj('welcome');
        showNodeDetails(tab[workingTab].currNodeId);
      }
      else {
        showObj('welcome');
        tab[workingTab].currNodeId = null;
        setInnerHTML("desc_section_" + tabIndex, "");
      }
    }
    if (i > 0 && !tab[workingTab].isolate)
      ; // addSearchOriginMarker(new GLatLng(tab[workingTab].searchLat, tab[workingTab].searchLon));
    else if (i == 0) {
      addResult(workingTab, '', '', getPhrase("no_results"), 0, 0, '', 0, '', false); // status
    }
  }
  else
    setVisibility('welcome', !currFound); // no data
}

// results code

function clearResults(tabName) {
  if (tabName == currMapTab) {
    clearData();
    finishedData();
    return;
  }
  else
    return;
  // ************* UNUSED CODE ****************
  var tabIndex = tab[tabName].tabIndex;
//  tab[tabName].results = new Array();
  tab[tabName].resultsHTML = '';
  tab[tabName].currNodeId = null;
  tab[tabName].currResultObjId = null;
  if (currMapTab == tabName) {
    if (chart) {
      chart.remove();
      chart = null;
    }
    setInnerHTML('my_chart', '');
    setInnerHTML('value_detail', '');
    showObj('welcome');
    if (map)
      map.clearOverlays();
    markers = new Array();
  }
  setInnerHTML('count_' + tabIndex, ' (0)'); // ''
  setInnerHTML('div_results_' + tabIndex, "<center>" + getPhrase('no_results') + "</center>"); // ''
  setInnerHTML('result_count_' + tabIndex, '');
  setInnerHTML('desc_section_' + tabIndex, '');
}

function addResult(tabName, nodeId, letter, title, latitude, longitude, distance, status, statusDesc, locked) {
  tab[tabName].resultsHTML += makeResultItem(tabName, nodeId, letter, title, latitude, longitude, distance, status, statusDesc, locked);
}

function selectResult(tabName, nodeId) {
  selectResultItem(tabName, nodeId);
}

function finishResults(tabName) {
  // add all in at once, set result height
  if (true) {
    var tabIndex = tab[tabName].tabIndex;
    var objId = 'div_results_' + tabIndex;
    var a = $(objId);
    a.scrollTop = 0; // less than 100% so that there is no horz scrollbar in Explorer
    var frontHtml = "<table id='result_table_" + tabName + "' class='sortable' cellpadding='0' cellspacing='0' border='0' width='96%'>";
    var endHTML   = "</table>";
    // setInnerHTML(objId, frontHtml + tab[tabName].resultsHTML + endHTML);

    if (tab[tabName].data.length) {
      setInnerHTML(objId, tab[tabName].resultsHTML);
      setInnerHTML('count_' + tabIndex, ' (' + tab[tabName].data.length + ')');
      setInnerHTML('result_count_' + tabIndex, tab[tabName].data.length + ' ');
      var sampleId = resultObjId(tabName, tab[tabName].data[0].id);
      if (a = $(sampleId)) { // define the results to be exactly 6 rows tall
        var results = $(objId);
        var lineHeight = a.scrollHeight; // 14
        results.style.height = (6 * lineHeight) + "px";
      }
    }
    else {
      setInnerHTML(objId, "<center>" + getPhrase('no_results') + "</center>"); // ''
      setInnerHTML('count_' + tabIndex, ' (0)');
      setInnerHTML('result_count_' + tabIndex, '');
    }
  }
}

function resultObjId(tabName, nodeId) {
  return 'result_' + tabName + '_' + nodeId;
}

function makeResultItem(tabName, nodeId, letter, title, latitude, longitude, distance, status, statusDesc, locked) {
  // debug("makeResultItem nodeId: " + nodeId);
//  tab[tabName].results[tab[tabName].results.length] = nodeId;
  var id = resultObjId(tabName,nodeId);
  var tabIndex = tab[tabName].tabIndex;
  var origNodeId = nodeId;
  nodeId = '"' + nodeId + '"';
  if (statusDesc != '')
    statusDesc = status + "-" + statusDesc;
  else
    statusDesc = getPhrase("status_na");
  if (title.length > 27) // ...
    title = title.substr(0, 27) + "&hellip;";
  if (statusDesc.length > 40) // ...
    statusDesc = statusDesc.substr(0, 40) + "&hellip;";
  var bgColor = bgColors[status];
  if (letter == '')
    letter = '&nbsp;';
  var lockClass = locked ? "locked" : "unlocked";
  return "<div id='" + id + "' style='background-color: " + bgColor + "'>" +
    "<span id='lock_" + tabName + "_" + origNodeId + "' class='" + lockClass + "' onclick='lockNode(" + nodeId + ");'>&nbsp;</span>" + 
    "<a href='#' onclick='showNodeDetails(" + nodeId + ",false);' ondblclick='gotoNode(" + nodeId + ")'>" +
    "<span class='col1'>&nbsp;" + letter + "</span>" + 
    "<span class='col2'>" + title + "</span>" +
    "<span class='col3'>" + statusDesc + "</span>" +
    "<span class='col4' id='dist_" + tabIndex + "_" + origNodeId + "'>" + distance + "</span>" +
    "</a><br/>" +
    "</div>";
}

function selectResultItem(tabName, nodeId) {
  noFocus();
  hideObj('welcome');
  var a;
  var currResultObjId;
  if (currResultObjId = tab[tabName].currResultObjId)
    if (a = $(currResultObjId))
      a.className = '';
  currResultObjId = resultObjId(tabName, nodeId);
  if (a = $(currResultObjId)) {
    tab[tabName].currNodeId = nodeId;
    tab[tabName].currResultObjId = currResultObjId;
    a.className = 'selectedItem';
    
    // showAdminTab('description');
    if (objVisible('values')) // Oct 11, 2008
      showAdminTab('values');
    else
      showAdminTab('description');

    // make it visible
    var tabIndex = tab[tabName].tabIndex;
//    for (i = 0; i < tab[tabName].results.length; i++) {
    for (i = 0; i < tab[tabName].data.length; i++) {
      // debug("selectResultItem nodes: " + tab[tabName].results[i]);
      // if (tab[tabName].results[i] == nodeId) {
      if (tab[tabName].data[i].id == nodeId) {
        var resultIndex = i;
        debug("resultIndex: " + resultIndex);
        if (a = $('div_results_' + tabIndex)) {
          var lineHgt = $(resultObjId(tabName, nodeId)).scrollHeight; // 14 20;
          var lines   = a.clientHeight / lineHgt; // 5;
          var top     = a.scrollTop;
          debug("scrollTop: " + top);
          var bottom  = top + lines * lineHgt;
          var toShow  = resultIndex * lineHgt;
          if (toShow < top || toShow > bottom) {
            debug("not in sight, lineheight: " + lineHgt);
            a.scrollTop = Math.max(0, toShow - 2 * lineHgt);
          }
        }
        break;
      }
    }
  }
}

function showNodeValues() {
  debug("showNodeValues");
  // setInnerHTML('my_chart', '');
  setInnerHTML("my_chart", chartPlaceholder);
  if (!tab[currMapTab].currResultObjId)
    setInnerHTML('value_detail', getPhrase('select_a_result'));
  else {
    ajaxInner("action=node_values&node_id=" + tab[currMapTab].currNodeId, "value_detail");    
  }
}

// page load/unload code

window.onload = initPage;
window.onunload = unload;
window.onbeforeunload = beforeUnload;
window.onresize = onResize;

function newPromo() {
  var obj;
  var html = '';
  if (false) { // obj = $('promo_' + Math.floor(Math.random()*promoCount))) {
    html = '<a href="" onclick=' + "'" + 'showPromo("' + obj.href + '"); return false;' + "'" + ">" + obj.innerHTML + "</a>";
  }
  else {
    html = getPhrase('promo_' + Math.floor(Math.random()*promoCount));
    // <a href="help0.asp?help=897"><b>$20 Enrollment Gift</b></a>
    debug("newPromo: " + html);
    html = html.replace(' prepared="true"', ''); // yet another Explorer wierdism
    html = html.replace('href="', 'href="" onclick=' + "'" + 'showPromo("'); // String.fromCharCode(39)
    html = html.replace('">', '"); return false;' + "'" + '>');
  }
  debug("newPromo2: " + html);
  setInnerHTML('footer_center', html);
}

function showPromo(url) {
  showAdminTab('help');
  var obj;
  if (obj = $('help_iframe')) {
    obj.isLoaded = false;
    obj.src = url;
  }
} 

function initPage() {
  debug("userAgent: " + ua);
  readMyList();
  // buildMyListDropdowns();
  setVisibility("ctrl_admin", showAdmin);
  enableTooltips();
  newPromo();
  setValue('user_name', '');
  setValue('password', '');
  parseQueryString();
  setLoggedOnState(loggedOn);
  performTranslations(); // will call initPagePart2
}

function initPagePart2() {
  // called by performTranslations, as it's needed for other inits (eg: address...)
  buildMyListDropdowns();
  attachFormActions();
  initTabs();
  marker_size  = getCookieDef('marker_size', 'normal');
  marker_max   = 1 * getCookieDef('marker_max', 25);
  units        = getCookieDef('units', 'metric');
  onResize();
  initMap();
  // setTimeout("map.onResize();", 1000); // clue Google Maps to look at the new map div size
  debug("after initMap, currMapTab: " + currMapTab);
  initialRefresh();
  setValueTabVisibility(getValue("group_id_" + tab[currMapTab].tabIndex));
  if (message != '')
    alert(message);
  if (getParam['address'])
    showAddress(getParam['address']);
  if (getParam['item'])
    showAddress('$' + getParam['item']);
}

function initialRefresh() {
  if (getValue("search_id_1") != '') // wait until the dropdown exists, as we need value to do refresh
    refresh();
  else {
    setTimeout("initialRefresh();", 1000);
  }
}

function saveTabs() {
  // alert("saveTabs");
  // record the map zoom, lat, long, group and search into presets
  myList['presets']['default'] = buildListEntry('presets', 'default');
  writeMyList();  
  // alert("saveTabs done");
}

function unload() {
  saveTabs();
  GUnload();
}

function beforeUnload() {
  saveTabs();
}

function checkMarkerColor(region) {
//  if (getCookie('marker_color') == '')
//    alert("marker_color is blank: " + region);
}


function setLoggedOnState(on) {
  loggedOn = on;
  try {
    setValueTabVisibility(getValue("group_id_" + tab[currMapTab].tabIndex));
  }
  catch (e) {}
  setVisibility('login_ctrls', !on);
  setVisibility('logout_ctrls', on);
  // var title = pageTitle + "&nbsp;";
  var title = getPhrase("page_title") + " ";
  if (loggedOn)
    title += getPhrase('title_logged_on');
  else
    title += getPhrase('title_logged_off');
  title = trim(title);
  try {
    document.title     = title;
    top.document.title = title;
  }
  catch (e) {}
} 

function showChart(sensorType) {
  ajaxInner("action=node_chart&node_id="+tab[currMapTab].currNodeId+"&type="+sensorType, "");
}

function showHistory(sensorType) {
  if (chart) {
    chart.remove();
    chart = null;
  }
  ajaxInner("action=node_history&node_id="+tab[currMapTab].currNodeId+"&type="+sensorType, "my_chart");
}

function showLocation(location) {
  var coords    = location.split(',');
  var latitude  = coords[0];
  var longitude = coords[1];
  var point     = new GLatLng(latitude, longitude);
  var status    = 1; // it's history, so real status is unknown
  map.clearOverlays();
  map.panTo(point);
  // map.setCenter(point, map.getZoom());
  // addMarker(icon, id, point, label, title, distance, status);
  addMarker(tab[currMapTab].icon[status], 0, tab[currMapTab].currNodeId, point, '', '', '', status);
}

// No longer used

function showAlert(message) {
  setInnerHTML("dialog_text", message);
  // setInnerHTML("dialog_text", "<h1>Even <em>More</em> Rounded Corners With CSS</h1><p>Here is a very simple example dialog.</p><p>Note that if gradients are used, you will need a 'min-height' (or fixed height) rule on the body of the dialog. If these examples appear funny at the bottom, it is because they do not enforce the min-height rule.</p>");
  showObj("dialog_frame");
}


// cookie stuff: locations, objects, presets

function substrCount(str, searchvalue) {
  var i = 0;
  var count = -1;
  while (i >= 0) {
    count++;
    i = str.indexOf(searchvalue, i+searchvalue.length);
  }
  return count;
}

function addToSelect(sel, item, before) {
  if (typeof(before) == 'undefined')
    before = null;
  else if (!isExplorer)
    before = sel.options[before]; // explorer wants index, Firefox wants actual option
  debug('addToSelect: value, text: ' + item.value + ", " + item.text);
  try {
    sel.add(item, before); // standards compliant
  }
  catch(ex) {
    debug('addToSelect error: ' + ex.description);
    sel.add(item); // IE only - if there is no 'before'
  }
}

function newOption(value, text, kind) {
  text = text.replace("+", " ");
  var option = document.createElement('option');
  if (typeof(text) != 'undefined')
    option.text = text;
  if (typeof(value) != 'undefined')
    option.value = value;
  if (typeof(kind) != 'undefined')
    option.kind = kind;
  return option;
}

function assignFromArray() {
  // assignFromArray(Split(data, "@@"), object, "zoom", "lat"))
  var args   = assignFromArray.arguments;
  var params = new Array(); // build a copy as .arguments isn't a true array
  for (var i = 0; i < args.length; i++)
    params.push(args[i]);
  var source = params.shift();
  var dest   = params.shift();
  for (i = 0; i < params.length; i++)
    dest[params[i]] = source[i];
}

function assignFromArrayNamed() {
  // assignFromArray(Split(data, "@@"), object, "zoom", "lat"))
  var args   = assignFromArrayNamed.arguments;
  var params = new Array(); // build a copy as .arguments isn't a true array
  for (var i = 0; i < args.length; i++)
    params.push(args[i]);
  var source = params.shift();
  var dest   = params.shift();
  for (i = 0; i < params.length; i++)
    dest[params[i]] = source[params[i]];
}

var myList = new Array('locations','objects','presets');

function readMyList() {
  myList['locations'] = new Array();
  var cookie = getCookie("locations");
  if (cookie != '') {
    var temp = cookie.split('|||');
    for (var i in temp) {
      var item = temp[i];
      if (substrCount(item, "@@") == 4) {
        var parts = item.split("@@");
        var entry = new Object();
        entry.cookie = item;
        assignFromArray(parts, entry, "name", "id", "zoom", "latitude", "longitude");
        myList['locations'][parts[0]] = entry;
        for (var j in entry)
          debug("locations[" + parts[0] + "][" + j + "]=" + entry[j]);
      }
    }
  }
  // objects
  myList['objects'] = new Array();
  cookie = getCookie("myobjects");
  if (cookie != '') {
    var temp = cookie.split('|||');
    for (var i in temp) {
      var item = temp[i];
      if (substrCount(item, "@@") == 1) {
        var parts = item.split("@@");
        var entry = new Object();
        entry.cookie = item;
        assignFromArray(parts, entry, "name", "nodeId");
        myList['objects'][parts[0]] = entry;
        for (var j in entry)
          debug("objects[" + parts[0] + "][" + j + "]=" + entry[j]);
      }
    }
  }
  // presets
  myList['presets'] = new Array();
  cookie = getCookie("presets");
  if (cookie != '') {
    var temp = cookie.split('|||');
    for (var i in temp) {
      var item = temp[i];
      if (substrCount(item, "@@") == 6) {
        var parts = item.split("@@");
        var entry = new Object();
        entry.cookie = item;
        assignFromArray(parts, entry, "name", "markerColor", "groupId", "searchId", "zoom", "latitude", "longitude");
        myList['presets'][parts[0]] = entry;
        for (var j in entry)
          debug("presets[" + parts[0] + "][" + j + "]=" + entry[j]);
      }
    }
  }
  // **** NOTE - if 'default' is not present, then it needs to be added, built on server
}

function writeMyList() {
  function concatEntry(entries) {
    var temp = '';
    var delim = '';
    for (var i in entries)
      if (entries[i] != null) {
        temp += delim + entries[i].cookie;
        delim = "|||";
      }
    return temp;
  }
  if (true) {
    debug("writeMyList 1");
    // don't forget to record tab1 => default, tab2 => tab2, etc.
    setCookie('locations', concatEntry(myList['locations']));
    debug("writeMyList 2");
    setCookie('myobjects', concatEntry(myList['objects']));
    debug("writeMyList 3");
    setCookie('presets',   concatEntry(myList['presets']));
    debug("writeMyList 4");
  }
}

function buildMyListDropdowns() {
  debug('buildMyListDropdowns');
  // first few items are 'new location, new object, ...
  // note that there are 5 of these, settings_id_1... settings_id_5.
  // maybe include the lat, long, etc, onto the item object, so there is no need for a lookup
  //   OR we create an associative array with keys that match the value for the dropdown items, and settings there.
  //      key may be the name and the kind
  // MAYBE and up/down arrow to change the order of items in the list
  
  // http://www.w3schools.com/htmldom/dom_obj_select.asp
  
  // newOption
  
  var i;
  for (var tabIndex = 1; tabIndex <= 5; tabIndex++) {
    var obj = $('settings_id_' + tabIndex);
    // clear out dropdowns
    for (var j = obj.options.length - 1; j >= 0; j--)
      obj.remove(j);

    debug('buildMyListDropdowns 2');
    addToSelect(obj, newOption('__new__', getPhrase('new_loc'), 'locations'));
    debug('buildMyListDropdowns 3');
    addToSelect(obj, newOption('__new__', getPhrase('new_obj'),   'objects'));
    debug('buildMyListDropdowns 4');
    addToSelect(obj, newOption('__new__', getPhrase('new_pre'),   'presets'));
    debug('buildMyListDropdowns 5');

    for (i in myList['presets']) {
      var name  = myList['presets'][i].name;
      var title = name;
      if (trim(title) == '')
        title = 'default';
      addToSelect(obj, newOption(name, title + " " + getPhrase("(preset)"), 'presets'));
    }
    debug('buildMyListDropdowns 6');
    for (i in myList['objects']) {
      var name = myList['objects'][i].name;
      addToSelect(obj, newOption(name, name + " " + getPhrase("(object)"), 'objects'));
    }
    debug('buildMyListDropdowns 7');
    for (i in myList['locations']) {
      var name = myList['locations'][i].name;
      addToSelect(obj, newOption(name, name + " " + getPhrase("(location)"), 'locations'));
    }
    debug('buildMyListDropdowns 8');
  }
}

function runMyListItem() {
  // based on currently selected item in list
  debug("runMyListItem");
  var selectObj;
  if (selectObj = $('settings_id_' + tab[currMapTab].tabIndex)) {
    var index = selectObj.selectedIndex;
    var item = selectObj.options[index];
    if (item.value == '__new__')
      newMyListItem();
    else {
      debug("runMyListItem index, kind, value: " + index + ", " + item.kind + ", " + item.value);
      // debug("runMyListItem2 name: " + ", " + myList[item.kind][item.value].name); // __new__ isn't an entry
      var setting = myList[item.kind][item.value];
      switch (item.kind) {
        case "objects":
          var address = "$" + setting.nodeId;
          setValue("address_" + tab[currMapTab].tabIndex, address);
          addressFocus(); 
          showAddress(address);
          break;
        case "locations":
          debug("run location: lat, long, zoom: " + setting.latitude + ", " + setting.longitude + ", " + setting.zoom);
          map.setCenter(new GLatLng(setting.latitude, setting.longitude), 1 * setting.zoom);
          setValue("address_" + tab[currMapTab].tabIndex, '');
          performSearch(tab[currMapTab].tabIndex, false);
          break;
        case "presets":
          debug("run preset: lat, long, zoom: " + setting.latitude + ", " + setting.longitude + ", " + setting.zoom);
          setValue("address_" + tab[currMapTab].tabIndex, '');
          map.setCenter(new GLatLng(setting.latitude, setting.longitude), 1 * setting.zoom);
          
          debug("run preset2: groupId, searchId: " + setting.groupId + ", " + setting.searchId);
          var tabIndex = tab[currMapTab].tabIndex;
          pendingSearchId = setting.searchId;  // set this after search is built (ie: in ajax code)
          setValue("group_id_" + tabIndex, setting.groupId);
          buildSearch(tabIndex);

          // assignFromArray(parts, entry, "name", "markerColor", "groupId", "searchId", "zoom", "latitude", "longitude");
          // how do we set both search and group id when group list hasn't been built yet?
          /*
            1. set group_id, which normally gets the search dropdown to be rebuilt (disable that)
            2. build the search dropdown
            3. perform the search
          */

          break;      
      }
    }
  }
}

function deleteMyListItem() {
  debug("deleteMyListItem");
  var selectObj, selIndex, tabIndex;
  tabIndex = tab[currMapTab].tabIndex;
  if (selectObj = $('settings_id_' + tabIndex))
    if (selIndex = selectObj.selectedIndex)
      if (selIndex > 2)
        if (confirm(getPhrase("delete_list_item"))) {
          var item = selectObj.options[selIndex];
          myList[item.kind][item.value] = null;
          writeMyList();
          selectObj.remove(selIndex);
        }
}

function nextLocationId() {
  var locationId = 10;
  if (myList['locations'].length)
    for (var i in myList['locations']) {
      var locId = myList['locations'][i].id;
      if (locId >= locationId)
        locationId = locId + 1;
    }
  debug("nextLocationId: " + locationId);
  return locationId;
}

// grab all settings that might be needed to build into a preset, object or location

function getEntryData(name) {
  // debug("getEntryData name, currMapTab: " + name + ", " + currMapTab);
  var tabIndex      = tab[currMapTab].tabIndex;
  var obj           = new Object();
  var center        = map.getCenter();
  obj['name']        = name;
  obj['zoom']        = map.getZoom();
  obj['latitude']    = center.lat();
  obj['longitude']   = center.lng();
  obj['groupId']     = getValue("group_id_"  + tabIndex);
  obj['searchId']    = getValue("search_id_" + tabIndex);
  obj['markerColor'] = "";
  obj['nodeId']      = "";
  obj['id']          = nextLocationId();
  if (tab[currMapTab].currNodeId)
    obj['nodeId'] = tab[currMapTab].currNodeId;
  return obj;
}

function buildListEntry(kind, name) {
  debug("buildListEntry: " + kind + ", " + name);
  var parts = getEntryData(name);
  var entry = new Object();

  debug("buildListEntry 2");
  switch (kind) {
    case "presets":
      assignFromArrayNamed(parts, entry, "name", "markerColor", "groupId", "searchId", "zoom", "latitude", "longitude");
      break;
    case "objects":
      assignFromArrayNamed(parts, entry, "name", "nodeId");
      break;
    case "locations":
      assignFromArrayNamed(parts, entry, "name", "id", "zoom", "latitude", "longitude");
      break;
  }
  debug("buildListEntry 3");
  // entry.cookie = entry.join("@@");
  var temp = "";
  var delim = "";
  for (var name in entry) {
    temp += delim + entry[name];
    delim = "@@";
  }
  entry.cookie = temp;
  debug("buildListEntry 4: " + temp);
  return entry;    
}

function saveMyListItem(kind, kindIndex) {
  debug("saveMyListItem");
  var selectObj;
  if (typeof(kind) != 'string') { // called from initTab with event object
    // based on currently selected item in list, might be new, might be update
    var tabIndex = tab[currMapTab].tabIndex;
    if (selectObj = $('settings_id_' + tabIndex)) {
      debug("saveMyListItem 2");
      var selIndex = selectObj.selectedIndex;
      if (selIndex > 2) { // over-write
        debug("saveMyListItem 3");
        var item = selectObj.options[selIndex];
        kind = item.kind;
        var name = item.value;
        // ****** TODO - how do we make sure that this entry shows up early in list??
        myList[kind][name] = buildListEntry(kind, name);
        debug("saveMyListItem 4");
        writeMyList();
        debug("saveMyListItem 5");
      }
      else
        newMyListItem();
    }
  }
  else {
    // from a button somewhere, like the node detail tab
    newKind = kind;
    newKindIndex = kindIndex;
    newLabel = '';
    switch(newKindIndex) {
      case 0: newLabel = "location"; break;
      case 1: newLabel = "object";   break;
      case 2: newLabel = "preset";   break;
    }
    IEprompt("Enter name for new " + newLabel, "");
  }
}

var newKindIndex = -1;
var newLabel = '';
var newKind  = '';

function newMyListItem() {
  debug("newMyListItem");
  var selectObj = $('settings_id_' + tab[currMapTab].tabIndex);
  var selIndex = selectObj.selectedIndex;
  newKind = selectObj.options[selIndex].kind;
  newLabel = '';
  switch(selIndex) {
    case 0: newLabel = "location"; break;
    case 1: newLabel = "object";   break;
    case 2: newLabel = "preset";   break;
  }
  IEprompt("Enter name for new " + newLabel, "");
  // results returned to promptCallback
}

function newMyListItemFinish(name) {
  var selectObj = $('settings_id_' + tab[currMapTab].tabIndex);
  if (name != null && name != "") {
    // ************** TODO: MUST ADD TO SELECT IN EACH TAB ************
    // ****** TODO - how do we make sure that this entry shows up early in list??
    var kind = newKind;
    var option = newOption(name, name + " (" + newLabel + ")", kind)
    try {
      if (myList[kind][name])
        ;
      else {
        myList[kind][name] = buildListEntry(kind, name);
        addToSelect(selectObj, option, 3);
        writeMyList();
      }
    }
    catch (e) {}
  }
}

function promptCallback(name) {
  newMyListItemFinish(name);
}

// ************** Microsoft broke Prompt for 'security' reasons
// http://www.hunlock.com/blogs/Working_around_IE7s_prompt_bug,_er_feature

var _dialogPromptID=null;
var _blackoutPromptID=null;

function IEprompt(innertxt,def) {

   that=this;

   var _isIE7=(navigator.userAgent.indexOf('MSIE 7')>0);

   this.wrapupPrompt = function (cancled) {
      if (_isIE7) {
         val=document.getElementById('iepromptfield').value;
         _dialogPromptID.style.display='none';
         _blackoutPromptID.style.display='none';
         document.getElementById('iepromptfield').value = '';
         if (cancled) { val = '' }
         promptCallback(val);
      }
      return false;
   }

   if (def==undefined) { def=''; }

   if (_isIE7) {
      if (_dialogPromptID==null) {
         var tbody = document.getElementsByTagName("body")[0];
         tnode = document.createElement('div');
         tnode.id='IEPromptBox';
         tbody.appendChild(tnode);
         _dialogPromptID=document.getElementById('IEPromptBox');
         tnode = document.createElement('div');
         tnode.id='promptBlackout';
         tbody.appendChild(tnode);
         _blackoutPromptID=document.getElementById('promptBlackout');
         _blackoutPromptID.style.opacity='.9';
         _blackoutPromptID.style.position='absolute';
         _blackoutPromptID.style.top='0px';
         _blackoutPromptID.style.left='0px';
         _blackoutPromptID.style.backgroundColor='#555555';
         _blackoutPromptID.style.filter='alpha(opacity=90)';
         _blackoutPromptID.style.height=(document.body.offsetHeight<screen.height) ? screen.height+'px' : document.body.offsetHeight+20+'px'; 
         _blackoutPromptID.style.display='block';
         _blackoutPromptID.style.zIndex='50';
         _dialogPromptID.style.border='2px solid blue';
         _dialogPromptID.style.backgroundColor='#DDDDDD';
         _dialogPromptID.style.position='absolute';
         _dialogPromptID.style.width='330px';
         _dialogPromptID.style.zIndex='100';
      }
      var tmp = '<div style="width: 100%; background-color: blue; color: white; font-family: verdana; font-size: 10pt; font-weight: bold; height: 20px">Input Required</div>';
      tmp += '<div style="padding: 10px">'+innertxt + '<BR><BR>';
      tmp += '<form action="" onsubmit="return that.wrapupPrompt()">';
      tmp += '<input id="iepromptfield" name="iepromptdata" type=text size=46 value="'+def+'">';
      tmp += '<br><br><center>';
      tmp += '<input type="submit" value="&nbsp;&nbsp;&nbsp;OK&nbsp;&nbsp;&nbsp;">';
      tmp += '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
      tmp += '<input type="button" onclick="that.wrapupPrompt(true)" value="&nbsp;Cancel&nbsp;">';
      tmp += '</form></div>';
      _blackoutPromptID.style.height=(document.body.offsetHeight<screen.height) ? screen.height+'px' : document.body.offsetHeight+20+'px'; 
      _blackoutPromptID.style.width='100%';
      _blackoutPromptID.style.display='block';
      _dialogPromptID.innerHTML=tmp;
      _dialogPromptID.style.top=parseInt(document.documentElement.scrollTop+(screen.height/3))+'px';
      _dialogPromptID.style.left=parseInt((document.body.offsetWidth-315)/2)+'px';
      _dialogPromptID.style.display='block';
      document.getElementById('iepromptfield').focus();
   } else {
      promptCallback(prompt(innertxt,def));
   }
}
