/*****************************************************
# $Id: core.js,v 1.241 2009/09/21 20:50:15 pblankenbaker Exp $

 JavaScript file to add NST headers with next/prev links
*****************************************************/

//
// domTT Global Variables
//
// Default: Set class for domTT tooltips...
var domTT_styleClass = 'niceTitle';

// Newer versions of domLib (from domMenu package) renamed
// the "detect collisions" function, put in a wrapper to
// keep the domTT package happy.
function domLib_detectCollisions(in_object, in_recover, in_useCache) {
  domLib_detectObstructions(in_object, in_recover, in_useCache);
}

//
// NST WUI Global Variables...
//
// sectCnt - Used to keep track of section headers in addSectionLink()
// sid - Session ID (set farther down in this file)
var sectCnt=1;

// Assume page can handle "smart page reloads" (like positionReload()).
var smartReload = true;

//
// Determines if Netscape/Mozilla/Firefox browser is being used.
function isNetscape() {

  return (navigator.appName == "Netscape");
}

//
// Determines if IE browser is being used...
function isIE() {
  // Starting with IE 9, you don't need the "isIE()" hacks, so
  // we will return false starting with that version
  if ((navigator.appName == "Microsoft Internet Explorer") &&
      (navigator.appVersion.indexOf('MSIE 9') == -1)) {
    window.isIE = function() { return true; }
    return true;
  } else {
    window.isIE = function() { return false; }
    // Set flag that this is a IE9 system
    window.isIE9 = true;
    return false;
  }
}

//
// Determines if IE8 browser is being used...
function isIE8() {

  if (isIE()) {
    return (navigator.appVersion.indexOf('MSIE 8') != -1);
  } else {
    return false;
  }
}

//
// get browser agent info object and set to lower case...
  var _userAgent = navigator.userAgent.toLowerCase();

//
// Determines if browser is Mozilla Firefox...
function isFirefox() {
  return (_userAgent.indexOf('firefox') != -1);
}

//
// Determines if browser is Google Chrome...
function isChrome() {
  return (_userAgent.indexOf('chrome') != -1);
}

//
// Determines if browser is Safari...
function isSafari() {
  return (_userAgent.indexOf('safari') != -1);
}

//
// Determines if browser is running on Windows...
function isWindows() {
  return (_userAgent.indexOf('windows') != -1);
}

//
// Determines if browser is running on Linux...
function isLinux() {
  return (_userAgent.indexOf('linux') != -1);
}

//
// Determines if browser is running on a Macintosh...
function isMac() {
  return (_userAgent.indexOf('macintosh') != -1);
}

//
// Determines if browser is running on an iPod...
function isiPod() {
  return (_userAgent.indexOf('ipod') != -1);
}

//
// Determines if browser is running on a BlackBerry PDA...
function isBlackberry() {
  return (_userAgent.indexOf('blackberry') != -1);
}

//
// Returns true if we know that we are in quirks mode
function isInQuirksMode() {
  return (document.compatMode == 'BackCompat');
}


//
// Default: Postpone DOM tooltips until page is loaded
var domTT_postponeActivation = true;
//
// Permit "sneaky" tooltip enabling for load average when NOT using IE
// (IE has a "sometimes" error when DOM tooltips are enabled prior to
// the page being fully loaded).
var nst_domTT_loadHoverActivate = ! isIE();
//
// Check for user session override...
try {
  if (_SESSION['domTT_postponeActivation']) {
    if (_SESSION['domTT_postponeActivation'] == "false") {
      domTT_postponeActivation = false;
    }
  }
} catch (e) {
}

//
// Allow the DOM Tooltip delay to be overridden by user...
//
// Default: Initially set tooltip delay to: '1.000 secs'...
domTT_activateDelay = 1000;
//
// Set long initial tooltip delay (1 min) for the
// Apple iPod iPhone/iTouch device...
if (isiPod()) {
  domTT_activateDelay = 60000;
}
//
// Check for user session override...
try {
  if (_SESSION['domTT_activateDelay']) {
    domTT_activateDelay = _SESSION['domTT_activateDelay'];
  }
} catch (e) {
}


/* setContent(id, content)
 *
 * Sets the content of element having id of 'id' to the
 * specified 'content'.
 *
 * id - ID of element to update (if null or element not found, nothing done).
 *
 * content - New content (if null, nothing done). */
function setContent(id, content) {
  if ((content == null) || (id == null) || (!document.getElementById)) {
    return false;
  }

  var entity = document.getElementById(id);
  if (entity == null) {
    return false;
  }

  if (document.all) {
    //
    // Use non-DOM method (IE)
    entity.innerHTML = content;
  } else {
    //
    // Use DOM method (firefox)
    var rng = document.createRange();
    rng.setStartBefore(entity);
    while (entity.hasChildNodes()) {
      entity.removeChild(entity.lastChild);
    }
    entity.appendChild(rng.createContextualFragment(content));
  }

  return true;
}


/* getContent(id)
 *
 * Gets the content of element having id of 'id'.
 *
 * id - ID of element to get info from (if null or element not found,
 * nothing done).
 *
 * returns - Content (or null if not found). */
function getContent(id) {
  if ((id == null) || (!document.getElementById)) {
    return null;
  }

  var entity = document.getElementById(id);
  if (entity == null) {
    return null;
  }

  return entity.innerHTML;
}


//
// Replaces '&', '<', '>' and '"' with HTML escape codes.
function escapeHtml(s) {
  return s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt').replace('"','&quot;');
}


/* Change class from 'odd'/'even' to 'oddover'/'evenover' for all
 * child elements.
 *
 * tr - A <tr> entity to modify (contained cells will be modified).
 *
 * evenover - Optional name of evenover class (defaults to 'evenover' if
 * omitted.
 *
 * oddover - Optional name of oddover class (defaults to 'oddover' if
 * omitted.
 */
function enterRow(tr, evenover, oddover) {
  if (oddover == null) oddover = 'oddover';
  if (evenover == null) evenover = 'evenover';

  var n = tr.cells.length;
  for (var i=0; i < n; i++) {
    entity = tr.cells.item(i);
    if (entity.className == 'odd') {
      entity.className = oddover;
    } else if (entity.className == 'labeledRow') {
      entity.className = 'labeledRowOver';
    } else {
      entity.className = evenover;
    }
  }
}


/* Change class from 'oddover'/'evenover' to 'odd'/'even' for all
 * child elements.
 *
 * tr - A <tr> entity to modify (contained cells will be modified).
 *
 * evenover - Not used.
 *
 * oddover - Optional name of oddover class (defaults to 'oddover' if
 * omitted.
 */
function exitRow(tr, evenover, oddover) {
  if (oddover == null) oddover = 'oddover';

  var n = tr.cells.length;
  for (var i=0; i < n; i++) {
    entity = tr.cells.item(i);
    if (entity.className == oddover) {
      entity.className = 'odd';
    } else if (entity.className == 'labeledRowOver') {
      entity.className = 'labeledRow';
    } else {
      entity.className = 'even';
    }
  }
}


//
// BEGIN - Cookie management functions...
//
// These set of functions are used to manage cookies within a web browser...

// setCookie(name, value, [expires], [path], [domain], [secure])
//        name - name of the cookie
//       value - value of the cookie
//   [expires] - optional: expiration date of the cookie
//               (defaults to end of current session)
//      [path] - path for which the cookie is valid
//               (defaults to path of calling document)
//    [domain] - domain for which the cookie is valid
//               (defaults to domain of calling document)
//    [secure] - Boolean value indicating if the cookie transmission requires
//               a secure transmission
//
//   Desscription: Cookie creation funtion...
//
//          Notes: 1) an argument defaults when it is assigned null as a placeholder
//                 2) a null placeholder is not required for trailing omitted arguments
//
//       Returns: N/A       
function setCookie(name, value, expires, path, domain, secure) {
  var curCookie = name
                  + "="
                  + escape(value)
                  + ((expires) ? "; expires="
                  + expires.toGMTString() : "")
                  + ((path) ? "; path=" + path : "")
                  + ((domain) ? "; domain=" + domain : "")
		  + ((secure) ? "; secure" : "");

  document.cookie = curCookie;
}


//
// getCookie(name)
//
//   name - name of the desired cookie
//
//   Description: return string containing value of specified cookie or null
//                if cookie does not exist...
//
//       Returns: The value of cookie 'name'...       
function getCookie(name) {
  var dc = document.cookie;
  var prefix = name + "=";
  var begin = dc.indexOf("; " + prefix);

  if (begin == -1) {
    begin = dc.indexOf(prefix);
    if (begin != 0) {
      return null;
    }
  } else {
    begin += 2;
  }

  var end = document.cookie.indexOf(";", begin);
  if (end == -1)
    end = dc.length;
  return unescape(dc.substring(begin + prefix.length, end));
}


//
// deleteCookie(name, [path], [domain])
//   name     - name of the cookie
//   [path]   - optional: path of the cookie (must be same as path used to create cookie)
//   [domain] - optional: domain of the cookie (must be same as domain used to create cookie)
//
//   Description: path and domain default if assigned null or omitted if no explicit
//                argument proceeds...
//
//       Returns: N/A       
function deleteCookie(name, path, domain) {
  if (getCookie(name)) {
    document.cookie = name
                    + "="
                    + ((path) ? "; path=" + path : "")
                    + ((domain) ? "; domain=" + domain : "")
                    + "; expires=Thu, 01-Jan-70 00:00:01 GMT";
  }
}

//
// getCookieMinutes()
//
// Returns the life of a NST WUI cookie (in minutes).
function getCookieMinutes() {
  var mins = 43200;      // Default: 30 * 24 * 60 = 43200  => 30 days...

  //
  // Check for global NST session config timeout override...
  try {
    mins = parseInt(sysInfo['SESSION_CONFIG_TIMEOUT']);
  } catch (e) {
  }

  return mins;
}

//
// storeSessionId()
//
//   Description: This function creates a globally unique NST Session ID (sid).
//		  This session ID is used for server side configuration
//		  uniqueness so that mutiple browsers querying an NST
//		  probe can have their own configuration persu=istence data set.
//
//        Format: 'sid' format - "sid_" + "now.getTime()" + "_" + "6 digit random number"
//                Example: "sid_1134261488336_012345"
//
//          Note: Currently we are setting the 'sid' to expire in one month...
//
//       Returns: The value of the newly created 'sid'...       
function storeSessionId() {

  // expire the NST session ID cookie: "sid" 1 year from now...
  var etime = new Date();
  var random_num_str = "";

  // create a random number between: "0 - 999999"...
  var random_num = Math.round(Math.random() * 1000000);

  random_num_str = random_num.toString();

  // force 6 digit length prepending any necessary leading zeros...
  while (random_num_str.length < 6) {
    random_num_str = "0" + random_num_str;
  }

  // format: 'sid'...
  var sid = "sid_"
            + etime.getTime()
            + "_"
            + random_num_str;

  //
  // Set NST session cookie expiration based on cuurent
  // server time plus the session configuration timeout value...
  //
  // Note: getTime() - returns time in milliseconds...
  etime.setTime(etime.getTime() + getCookieMinutes() * 60 * 1000);

  //
  // create cookie...
  //
  // return cookies for 'secure' sessions only (HTTPS)...
  // setCookie('NST_Session_ID', sid, etime, '/', '', 'secure');
  //
  // return cookies for 'any' type of session...
  setCookie('NST_Session_ID', sid, etime, '/', '', '');

  var sid_get = getCookie('NST_Session_ID');

  // If we successfully stored a new cookie, reload the page
  if (sid_get) {
    window.location.reload();
  }

  return sid;
}


//
// getSessionId()
//
//   Description: This function will try to get the NST Session ID (sid)
//                for this browser. If the 'sid' is not found one
//                will be created. If a NST Session ID is found, update
//                the 'Cookie' session expiration time to the current
//                server time plus the session configuration timeout
//                value. Also update the PHP Session ID 'Cookie'
//                expiration time...
//
//       Returns: The value of a new or current NST Session ID...       
function getSessionId() {

  //
  // Update NST session ID...
  var sid = getCookie("NST_Session_ID");

  // Update the NST Session ID cookie expiration time if able to get ID...
  if (sid) {
    var etime = new Date();
  //
  // Set NST session ID cookie expiration based on current
  // server time plus the session configuration timeout value...
    etime.setTime(etime.getTime() + getCookieMinutes() * 60 * 1000);
    setCookie('NST_Session_ID', sid, etime, '/', '', '');

  //
  // Also update PHP session ID cookie expiration time if available...
    var pid = getCookie("PHPSESSID");
    if (pid) {
      setCookie('PHPSESSID', pid, etime, '/', '', '');
    }
  } else {
    return unescape(storeSessionId());
  }
  return unescape(sid);
}

// END - Cookie management functions...


//
// basename(path)
//
//   Determines the base file name given a full path.
function basename(path) {
  var spos = path.lastIndexOf("/");
  if (spos != -1) {
    return path.substring(spos + 1);
  }
  return path;
}


//
// toggleVisibility(id)
//
//   Toggles the CSS 'display' attribute from 'block' to 'none' (or vice
//   versa to make a block of HTML output visible (or invisible). Useful
//   when creating "pull-downs".
function toggleVisibility(id) {
  var entity = document.getElementById(id);
  if (entity.style.display == 'none') {
    entity.style.display = 'block';
  } else {
    entity.style.display = 'none';
  }
}


//
// toggleOpenClose(iconid, id)
//
// Toggles visibility of block identified by 'id' and changes icon
// image (22x22) between dir-close.gif and dir-open.gif. Useful in
// creating areas that look like directory trees that can be opened and
// closed.
function toggleOpenClose(iconid, id) {
  var entity = document.getElementById(id);
  var icon = document.getElementById(iconid);
  if (entity.style.display == 'none') {
    entity.style.display = 'block';
    icon.src = '/nst/images/folder.open.gif';
  } else {
    entity.style.display = 'none';
    icon.src = '/nst/images/folder.gif';
  }
}


//
// setOpenClose(iconid, id, displayState)
//
// Set visibility of block identified by 'id' and changes icon
// image (22x22) between dir-close.gif and dir-open.gif.
//
// displayState - 'none' - close or 'display' - open...
function setOpenClose(iconid, id, displayState) {
  var entity = document.getElementById(id);
  var icon = document.getElementById(iconid);
  if (displayState == 'none') {
    entity.style.display = 'none';
    icon.src = '/nst/images/folder.gif';
  } else {
    entity.style.display = 'block';
    icon.src = '/nst/images/folder.open.gif';
  }
}


//
// getCheckedRadioValue(radioButton)
//
// Iterates through all of the radio buttons looking for the first
// checked (should only be one) and returns the associated value.
function getCheckedRadioValue(rb) {
  for (var i = 0; i < rb.length; i++) {
    if (rb[i].checked) {
      return rb[i].value;
    }
  }
  return ""; // Default to empty string if nothing checked
}


//
// getSelectedValueById(select_id)
//
//   Finds selected value of the <select id="select_id"> component
//   on the page (only works for selects where one item can be selected).
//
//   select_id - The id attribute assigned to the <select> entity.
function getSelectedValueById(select_id) {
  var e = document.getElementById(select_id);
  return e.options[e.selectedIndex].value;
}


//
// Parses arguments from URL location (uses current page if null
// passed).  Stores results in nave/value pairs returned as an
// object. From "JavaScript The Definitive Guide".
//
// u - URL Location to parse (optional)
//
// Example:
// var args = getArgs();
// if (args.pkg) {
//   document.write("<p>Package: <b>"+args.pkg+"</b></p>");
// }
function getArgs(u) {
  if (u == null) u = location;	// default to current page

  var args = new Object();
  var query = u.search.substring(1);	 // get query string
  var pairs = query.split(",");

  for(var i= 0; i < pairs.length; i++) {
    var pos=pairs[i].indexOf('=');	// look for name=value
    if (pos == -1) continue;
    var argname = pairs[i].substring(0,pos);
    var value = pairs[i].substring(pos+1);
    args[argname] = unescape(value);
  }
  return args;
}


//
// Inserts a section link anchor point (place to jump to)
// and then increments the global sectCnt variable.
function addSectionLink() {
  var id = "sectLink" + sectCnt;
  document.writeln("<a id=\"" + id + "\" name=\"" + id + "\"></a>");
  NstDom.registerSection(id);
  sectCnt++;
}


//
// Writes out nav link information
//
// url - Where to jump to
// idir - Where to find images (defaults to /nst/images/ if omitted)
// bitmap - name of bitmap
//
// text - Text to display as hover help (no double quotes), OR (it
// textWidth parameter omitted), the JavaScript function to call on
// the onmouseover event.
//
// w - width of bitmap
// h - height of bitmap
// onclick - optional onclick handler
//
// textWidth - width of domTT tooltip in pixels (if present, it will
// be used as the width of the the DOM tooltip).
function addNavLink(url,idir,bitmap,text,w,h,onclick,textWidth) {

  if ( idir == null ) {	// Where to location images
    idir="/nst/images/";
  }

  var aattrs=" class=\"navLinkA\"";
  if (url != null) {
    aattrs=aattrs + " href=\""+url+"\"";  
  }
  if (onclick != null) {
    aattrs=aattrs + " onclick=\""+onclick+"\" style=\"cursor: pointer;\"";
  }

  if (textWidth) {
    document.write("<a" + aattrs + " onmouseover=\"domTT_activate(this, event, 'content', '"+text+"', 'width', "+textWidth+");\">");
  } else {
    document.write("<a" + aattrs + " onmouseover=\"" + text + ";\">");
  }

  document.write('<img alt="" src="' + idir + bitmap + '"'
	+ ' style="width: ' + w + "px; height: " + h + 'px;"'
 	+ ' class="navLinkImg" />');

  document.write("</a>");
}


//
// Set the "Home Icon" tooltip, URL and offset within page
//
// url - URL to jump to
// tooltip - Tool tip to display
// [tag] - Optional Name of anchor to jump to (the "tag" in <a name="tag"></a>)
navLinkStartToolTip = "<span style=\\\&#39;color: #cc9900;\\\&#39;>Go</span> To The \\\&#39;<span style=\\\&#39;color: #33ff00;\\\&#39;>NST Start Page</span>\\\&#39; Page";
navLinkHomeToolTip = "<span style=\\\&#39;color: #cc9900;\\\&#39;>Go</span> To The \\\&#39;<span style=\\\&#39;color: #33ff00;\\\&#39;>NST WUI Index</span>\\\&#39; Page";
navLinkHomeUrl = "/";
navLinkBreadCrumbs = false;

function setNavLinkHome(url, tooltip, tag) {
  // Not legal to change if bread crumbs are enabled
  if (!navLinkBreadCrumbs) {
    if (tooltip != "") {
      navLinkHomeToolTip = tooltip;
    }
    navLinkHomeUrl = url;
    if (tag != null) {
      navLinkHomeUrl = navLinkHomeUrl + "#" + tag;
    }
  }
}


//
// Standard nav links (top, bottom, next, previous)
//
// idir - Root location to find bitmap images at (defaults to "/nst/images/"
// img - Optional bitmap file name to use (if you don't want default)
// cnt (next/prev only) - index of section to jump to (optional)
// url - Link to jump to (or null for default)
function addNavLinkHome(bitmap,idir,url) {
  if (bitmap == null) {
    bitmap="links_home.gif";
  }

  if (url == null) {
    url = navLinkHomeUrl;
  }

  addNavLink(url, idir, bitmap, navLinkHomeToolTip, 21, 22, null, 220);
}

function addNavLinkHomeFooter(bitmap, idir, url) {

  if (bitmap == null) {
    bitmap="links_footerhome.gif";
  }

  if (url == null) {
    url = navLinkHomeUrl;
  }

  addNavLink(url, idir, bitmap, navLinkHomeToolTip, 20, 23, null, 220);
}

function positionReload(hash) {

  // If page doesn't support smart reloads, let browser
  // decide what to do.
  if (!smartReload) {
    window.location.reload();
    return;
  }

  //
  // "#sectLink0" is alias for "#top"
  if (hash == "#sectLink0") {
    hash = "#top";
  }

  //
  // special refresh after submit post or non-submit (link)...
  if (window.location.search.length == 0) {
    var url = window.location.pathname + "?forcerefresh=" + startTime + hash;
    window.location.replace(url);
    return;
  }

  if (isIE()) {
    // Ugly hack for Internet Explorer

    var newloc = window.location.href;

    // If URL contains a "?#" or a '?' at end, trim it off - we don't
    // need it (and we keep getting it when the submit form hack is used).

    var hpos = newloc.indexOf("?#");
    if (hpos > 0) {
      newloc = newloc.substr(0,hpos);
    } else {
      hpos = newloc.indexOf("?");
      if (hpos == (newloc.length - 1)) {
        newloc = newloc.substr(0,hpos);
      }
    }

    // If URL contains form data information, we just tell browser to reload

    if (newloc.indexOf('?') >= 0) {

      // Trim off any trailing "#TAG" string
      hpos = newloc.lastIndexOf("#");
      if (hpos > 0) {
        newloc = newloc.substring(0,hpos);
      }

      // Trim off any trailing "forcerefresh=XXX" that may have been added
      var tpos = newloc.indexOf('forcerefresh=');
      if (tpos > 0) {
        newloc = newloc.substr(0, tpos - 1);
      }
    
      // Tack on new "&forcerefresh=XXX#TAG"
      if (newloc.indexOf('?') > 7) {
        newloc += '&';
      } else {
        // If no other parameters, then use '?' as its the first parameter
        newloc += '?';
      }

      newloc = newloc + 'forcerefresh=' + startTime + hash;
      window.location.replace(newloc);

    } else {

      // Since URL didn't contain any form values, we can use the submit form
      // hack to set the location to the current section.

      hpos = newloc.lastIndexOf("#");
      if (hpos > 0) {
        newloc = newloc.substring(0,hpos);
      }
      newloc += hash;
      document.forms.ieReloadHack.action = newloc;
      document.forms.ieReloadHack.submit();
    }

  } else {

  //
  // This is how its suppose to work...
    window.location.replace(hash);
    window.location.reload();
  }
}


//
// Reload the current window with the page...
function reloadWindow() {

  // If page doesn't support smart reloads, let browser
  // decide what to do.
  if (!smartReload) {
    window.location.reload();
    return;
  }

  //
  // special refresh after submit post or non-submit (link)...
  if (window.location.search.length == 0) {
    var url = window.location.pathname + "?forcerefresh=" + startTime;
    window.location.replace(url);
    return;
  }

  window.location.reload();
}


//
// Reload a frame in current window if the
// frame is found...
function reloadWindowFrame(frame) {
  if (window.frames[frame]) {
    window.location=window.frames[frame].location.href;
  } else {
    window.location.reload();
  }
}

//
// Function to open the given 'url' in a new browser
// tab or window...
function openUrlTabWindow(url) {
  window.open(url);
}


function addNavLinkReload(bitmap,idir,cnt) {
  if (cnt == null) cnt = sectCnt-1;
  if (bitmap == null) bitmap="links_reload.gif";

  var tooltip = "<span style=\\\&#39;color: #cc9900;\\\&#39;>Reload</span> The \\\&#39;<span style=\\\&#39;color: #33ff00;\\\&#39;>Current</span>\\\&#39; Page At This <span style=\\\&#39;color: #00ccff;\\\&#39;>Position</span>";
  var tooltipwidth = 280;

  if (!smartReload) {
    tooltip = "<span style=\\\&#39;color: #cc9900;\\\&#39;>Reload</span> Contents Of The \\\&#39;<span style=\\\&#39;color: #33ff00;\\\&#39;>Current</span>\\\&#39; Page";
    tooltipwidth = 260;
  }

  addNavLink(null, idir, bitmap, tooltip, 23, 22, "positionReload('#sectLink"+cnt+"')", tooltipwidth);
}

function addNavLinkToggleConsole(bitmap,idir) {
  try {
    if (!window.NstConsole) {
	return;
    }

    if (bitmap == null) bitmap="toggle_console.gif";
    addNavLink(null, idir, bitmap, "NstDom.showNstConsoleTooltip(this, event)", 19, 22, "NstConsole.toggleConsole()", false);
  } catch (e) {
  }
}

function addNavLinkTop(bitmap,idir) {
  if (bitmap == null) bitmap="links_top_arrow.gif";
  addNavLink(null,idir,bitmap,"<span style=\\\&#39;color: #cc9900;\\\&#39;>Go</span> To The \\\&#39;<span style=\\\&#39;color: #33ff00;\\\&#39;>Top</span>\\\&#39; Of The Page",15,23,"NstDom.scrollTo(document.body, true)",200);
}

function addNavLinkTopFooter(bitmap,idir) {
  if (bitmap == null) bitmap="links_footertop_arrow.gif";
  addNavLink(null,idir,bitmap,"<span style=\\\&#39;color: #cc9900;\\\&#39;>Go</span> To The \\\&#39;<span style=\\\&#39;color: #33ff00;\\\&#39;>Top</span>\\\&#39; Of The Page",17,23,"NstDom.scrollTo(document.body, true)",200);
}

function addNavLinkBottom(bitmap,idir) {
  if (bitmap == null) bitmap="links_bottom_arrow.gif";
  addNavLink(null,idir,bitmap,"<span style=\\\&#39;color: #cc9900;\\\&#39;>Go</span> To The \\\&#39;<span style=\\\&#39;color: #33ff00;\\\&#39;>Bottom</span>\\\&#39; Of The Page",15,23,"NstDom.scrollToBottom(document.body, 0, 0)",220);
}

function addNavLinkNext(bitmap,idir,cnt) {
  if (cnt == null) cnt = sectCnt;
  if (bitmap == null) bitmap="links_down_arrow.gif";
  var url = "#sectLink" + cnt;
  // var url = "javascript:NstDom.moveToNextSection('sectLink" + (cnt - 1) + "');";
  addNavLink(url,idir,bitmap,"<span style=\\\&#39;color: #cc9900;\\\&#39;>Go</span> To The \\\&#39;<span style=\\\&#39;color: #33ff00;\\\&#39;>Next</span>\\\&#39; Section",15,20,null,180);
}

function addNavLinkPrev(bitmap,idir,cnt) {
  if (cnt == null) cnt = (sectCnt-2);
  if (bitmap == null) bitmap="links_up_arrow.gif";

  // Jump to top unless count is not 0
  var prev = "#top";
  if (cnt != 0) {
    prev = "#sectLink" + cnt;
  }
  addNavLink(prev,idir,bitmap,"<span style=\\\&#39;color: #cc9900;\\\&#39;>Go</span> To The \\\&#39;<span style=\\\&#39;color: #33ff00;\\\&#39;>Previous</span>\\\&#39; Section",15,20,null,200);
}

function addNavLinkPrevFooter(bitmap,idir,cnt) {
  if (cnt == null) cnt = (sectCnt-2);
  if (bitmap == null) bitmap="links_footerprev_arrow.gif";
  addNavLink("#sectLink"+cnt,idir,bitmap,"<span style=\\\&#39;color: #cc9900;\\\&#39;>Go</span> To The \\\&#39;<span style=\\\&#39;color: #33ff00;\\\&#39;>Previous</span>\\\&#39; Section",17,23,null,200);
}


//
// Adds a section header to document:
//
// t - Title to display (may be blank)
// r - Optional anchor point (if you need to refer back)
// h - Optional header level (defaults to "h2" if omitted)
//   - This option is no longer used...
// idir - Directory with bitmaps (defaults to "/nst/images/" if omitted)
// homeurl - Address of home page
needReloadFormForIE = true;

function addSectionHeader(t,r,h,idir,homeurl) {

  if (isIE() && needReloadFormForIE) {
    needReloadFormForIE = false;
    document.writeln("<form name='ieReloadHack' action='MISSING'></form>");
  }

  if ( h == null ) {	// Default to level 2 header
    h = "h2";
  }

  var sectPrior = sectCnt-1; // Get previous section number

  if ( r != null ) {	// Insert anchor (name and id) if user passed one
    document.writeln("<a name=\""+r+"\" id=\""+r+"\"></a>")
  }

  if (t == null) {	// Permit blank titles
    t = "";
  }

			// Big Hairy block of HTML
//  document.write("<"+h+">");

  addSectionLink();      // Set our anchor point and increment section number

  document.write(
	"<table class=\"navheadertext\" border=\"0\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\">"+
         "<colgroup>"+
          "<col width=\"99%\">"+
          "<col width=\"1%\">"+
         "</colgroup>"+
	 "<tr>"+
	  "<td align=\"left\" style=\"padding-left: .2em;\">"+
	    t+
	  "</td><td align=\"right\" valign=\"bottom\" style=\"padding-right: .2em;\">"+
            "<span style=\"white-space: nowrap;\">");
  addNavLinkHome(null,idir,homeurl);
  addNavLinkReload(null,idir);
  addNavLinkTop(null,idir);
//  document.write("</td><td>");
  addNavLinkBottom(null,idir);
//  document.write("</td><td align=\"right\">");
  addNavLinkPrev(null,idir);
//  document.write("</td><td align=\"right\">");
  addNavLinkNext(null,idir);
  document.write("</span></td></tr></table>");
//	"</"+h+">");

}


//
// Adds a "collapsible" section header to document:
//
// title   - Title to display (may be blank)
// id      - The ID (for reference) for the section - must be unique
//           Also: The <div> section to toggle visibility on Must have
//           the ID of: [id + "-area"]
// istate  - Initial state ('none' for hidden, 'block' for display)
// hlevel  - Optional header level (defaults to "h2" if omitted)
//         - This option is no longer used...
// idir    - Directory with bitmaps (defaults to "/nst/images/" if omitted)
// homeurl - Address of home page
function addCollapsibleHeader(title, id, istate, hlevel, idir, homeurl) {

  var headerIcon = "<img id=\""
    + id + "-icon\" onmouseover=\"domTT_activate(this, event, 'content', '<span style=\\\&#39;color: #cc9900;\\\&#39;>Click</span> on icon to \\\&#39;<span style=\\\&#39;color: #33ff00;\\\&#39;>hide</span>/<span style=\\\&#39;color: #33ff00;\\\&#39;>show</span>\\\&#39; contents', 'width', 240);\""
    + " width=\"27\" height=\"22\" alt=\"\""
    + " style=\"border: none; margin-right: 5px; vertical-align: baseline; cursor: pointer;\""
    + " onclick=\"javascript:toggleOpenClose('" + id + "-icon', '"
    + id + "-area');\""
    + " src=\"/nst/images/";

  if (istate == 'none') {
    headerIcon += "folder.gif";
  } else {
    headerIcon += "folder.open.gif";
  }
  headerIcon += "\" />";

  // Print header block for table
  addSectionHeader(headerIcon + title, id, hlevel, idir, homeurl);
}

//
// Calculate an average DOM Tooltip length in pixels...
//
// val      - Value (string) name or (integer) number of chars in string...
// offset   - optional fixed character length offset value in (pixels)...
// minWidth - optional minimum width value (pixels) returned...
// maxWidth - optional maximum width value (pixels) returned...
//
// Return: an average DOM Tooltip length in pixels...
function calcDomTTLen(val, offset, minWidth, maxWidth) {

  var olen = 0;
  var vlen = 0;

  //
  // has a "val" param been passed?
  if (val != null) {
  //
  // determine value type...
    if (val.length) {
  //
  // calc value length (string)...
      vlen = val.length;
    } else {
  //
  // set value length (integer)...
      vlen = val;
    }
  }

  //
  // check for optional offset...
  if (offset != null) {
    olen = offset;
  }

  //
  // calc length using DomTT multipler for javascript...
  var clen = olen + (vlen * 7.9);

  //
  // return no less than "minWidth" width value if specified...
  if (minWidth != null) {
    if (clen < minWidth) {
      return minWidth;
    }
  }

  //
  // return up to "maxWidth" width value if specified...
  if (maxWidth != null) {
    if (clen > maxWidth) {
      return maxWidth;
    }
  }

  //
  // return an integer pixel value...
  return Math.round(clen);
}

// Optional declare entries into the 'sysInfo' array...
// sysInfo["NST_ISO_VERSION"]    - Version of ISO (like: "1.6.0")
// sysInfo["NST_ISO_BUILD_DATE"] - ISO Build date (text)
// sysInfo["NST_ISO_BUILD_DATE_SHORT"] - ISO Build date short format (text)
// sysInfo["NST_ISO_BUILD_DATE_SECS"] - ISO Build date as time_t (int)
// sysInfo["NST_WUI_VERSION"]    - Version of WUI (like: "1.6.0")
// sysInfo["NST_WUI_BUILD_DATE"] - WUI Build date (text)
// sysInfo["NST_WUI_BUILD_DATE_SHORT"] - WUI Build date short format (text)
// sysInfo["NST_WUI_BUILD_DATE_SECS"] - WUI Build date as time_t (int)
// sysInfo["NST_LIVECD_BOOT"] - true if booted from Live CD, false if hard disk
// sysInfo["NST_VM_BOOT"] - true if booted in VMware, false if not
// sysInfo["KERNEL"]             - Kernel information
// sysInfo["CPU_COUNT"]          - Number of CPUs/Cores
// sysInfo["CPU_MODEL"]          - CPU Model info
// sysInfo["CPU_SPEED"]          - CPU speed (floating point MHz)
// sysInfo["CPU_BOGOBIPS"]       - Bogo BIPS value (Bogo MIPS / 1000.0)
// sysInfo["CPU_CACHE"]          - String describing CPU cache
// sysInfo["RAM_TOTAL"]          - Total amount of RAM (in MB)

// Dynamic information that might appear in sysInfo...
// sysInfo["HOST_NAME"]     - Primary ASCII host name associate with system
// sysInfo["IP_ADDRESS"]    - Primary IP address associated with system
// sysInfo["NST_BOOT_TYPE"] - NST Live or Hard Disk Install (Virtual or Physical)
// sysInfo['NST_PRO_REG']   - NST Pro Subscribed status

// sysInfo["PAGE_SERVER_TIME"]   - Time of page creation at NST server...
// sysInfo["Server Create Time"] - Time of page rendering at client browser...

// sysInfo["UP_FORMAT"]     - Describe the format of the up time field on nav bar
// sysInfo["UP_DAYS"]       - Number of days (int) system has been up
// sysInfo["UP_HHMM"]       - Hours and minutes system up for current day

// sysInfo["LOAD_1MIN"]     - System load from last 1 minute period
// sysInfo["LOAD_5MIN"]     - System load from last 5 minute period
// sysInfo["LOAD_15MIN"]    - System load from last 15 minute period

// sysInfo["RUNNING_THREADS"]
//   Number of running threads on system (from /proc/loadavg)

// BEGIN fields set by AJAX reqeust of /nstwui/php/system/cpu-usage-ajax.php

// sysInfo["PROC_START"]    - Time of 1st reading of /proc/stat (seconds)
// sysInfo["PROC_END"]      - Time of 2nd reading of /proc/stat (seconds)

// sysInfo["PROC_FORKED"]   - Total number of processes forked by system
// sysInfo["PROC_RUNNING"]  - Processes currently running
// sysInfo["PROC_BLOCKED"]  - Processes currently blocked from running

// sysInfo["CPU_USAGE_COUNT"] - Number of CPUs we have usage information for.
//   NOTE: If set to a value larger than 0, we will have information for each 
//   CPU (CPU0, CPU1, ...), plus "summary" usage with the key of just "CPU".

// sysInfo["CPU"] - Summary usage
// sysInfo["CPU0"] - Usage for first core
// sysInfo["CPU1"] - Usage for second core 
// ...

// Each CPU usage summary is an array of the following:
// sysInfo["CPU"]["USAGE"] - Total usage in range of [0.0, 1.0]
// sysInfo["CPU"]["USER"] - Counts attributed to user time
// sysInfo["CPU"]["USER_NICE"] - Counts attributed to user "nice" time
// sysInfo["CPU"]["SYSTEM"] - Counts attributed to system time
// sysInfo["CPU"]["IDLE"] - Counts attributed to idle time
// sysInfo["CPU"]["IO_WAIT"] - Counts attributed to I/O wait time
// sysInfo["CPU"]["HARD_IRQ"] - Counts attributed to servicing hard IRQs
// sysInfo["CPU"]["SOFT_IRQ"] - Counts attributed to servicing soft IRQs
// sysInfo["CPU"]["STOLEN"] - Counts attributed to stolen virtualization time
// sysInfo["CPU"]["TOTAL"] - Total of all counts

// sysInfo["SESSION_CONFIG_TIMEOUT"]
//
//   How long session and cookie information should persist in minutes.

// END fields set by AJAX reqeust of /nstwui/php/system/cpu-usage-ajax.php

/* Function to build a single row in a tooltip table which will have the form:
 *
 * <tr>
 *   <th align="left">LABEL:</th>
 *   <td align="left"><span class="ttValue">VALUE</span></td>
 * </tr>
 *
 * NOTE: If either the LABEL or VALUE are null, then a empty string is returned.
 *
 * label    - Label to appear for table row
 * value    - Value to appear in table row
 * value_id - Optional ID to give value for dynamic DOM manipulation 
 */

//
// Global var used to hold the maximum char width of a "value"
// in a "keyLabels" array...
var keyLabelMaxDomTTWidth = 0;

function buildInfoTableRow(label, value, value_id) {
  if (label && value) {

  //
  // see if max char width in array...
  //
  // Note: Use a javascript reg expr: "/ +/g" to remove duplicate
  //       spaces in string when calculating the value len for HTML
  //       rendering of multiple spaces...
    if (value.toString().replace(/ +/g," ").length > keyLabelMaxDomTTWidth) {
      keyLabelMaxDomTTWidth = value.toString().replace(/ +/g," ").length;
    }

  //
  // highlight chars: '%,(,)'
    label = label.replace(/\(/,"<span style='color: white; font-weight: normal;'>(</span>");
    label = label.replace(/%/,"<span style='color: white; font-weight: normal;'>%</span>");
    label = label.replace(/\)/,"<span style='color: white; font-weight: normal;'>)</span>");

  //
  // Optional value ID...
  //
  // Only provide an ID if one was passed...
  var _val_id = '';
  if (typeof(undefined) != typeof(value_id)) {
    _val_id = ' id="' + value_id + '"';
  }

  //
  // build key/value DomTT row...
    return '<tr><th align="right"><span class="ttNote">' + label
	+ '</span><span class="ttNormal">:</span></th>'
        + '<td align="left"><span class="ttValue"' + _val_id + '>'
	+ value + '</span></td></tr>';
  }
  return "";
}

/* Function to build a entire table which will have the form:
 *
 *   <tr>
 *     <th class="ROW[0,0]">ROW[1,0]</th>
 *     <th align="ROW[0,1]">ROW[1,1]</th>
 *     ...
 *   </tr>
 *   <tr>
 *     <td class="ROW[2,0]">ROW[3,0]</td>
 *     <td class="ROW[2,1]">ROW[3,1]</td>
 *     ...
 *   </tr>
 *   <tr>
 *     <td class="ROW[2,0]">ROW[4,0]</td>
 *     <td class="ROW[2,1]">ROW[4,1]</td>
 *     ...
 *   </tr>
 *   ...
 * </tr>
 *
 * You pass this function an array of arrays like the following:
 *
 * var rows = [
 *   [ "ttHeadRight", "ttHeadRight", "ttHeadRight" ],
 *   [ "Year", "Rate", "Balance" ],
 *   [ "ttValue", "ttValue", "ttValue" ],
 *   [ 2006, "4.5%", 1000 ],
 *   [ 2007, "4.6%", 1045 ]
 * ];
 *
 * The first row indicates the class for each of the <td> elements.
 * The second row is treated as a header row (<th>), but ONLY if the
 * 'header' parameter is set to true.
 *
 * NOTE: If either the LABEL or VALUE are null, then a empty string is returned.
 *
 * data - Array of rows of information for table.
 * header - Pass true if second row is a row of headers (th), pass false
 * if its data.
 */

function buildTableRows(table, header) {
  var results = "";
  if (!table || (table.length < 2)) {
    return results;
  }

  var classes = table[0];

  for (var i = 1; i < table.length; i++) {
    var row = table[i];
    results += "<tr>";

    for (var j = 0; j < row.length; j++) {
      if ((i == 1) && header) {
	results += "<th class=\"" + classes[j] + "\">" + row[j] + "</th>";
      } else {
	results += "<td class=\"" + classes[j] + "\">" + row[j] + "</td>";
      }
    }

    // If header, printed, load classes for data (and skip classes row)
    if (header && (i == 1)) {
	i++;
	classes = table[i];
    }

    results += "</tr>";
  }
  return results;
}

/* Function to build a HTML table of key/value pairs from the sysInfo[]
 * array.
 *
 * keyLabels - An array of KEY/LABEL pairs like:
 *
 *   var kl = new Array(KEY0, LABEL0 [, KEY1, LABEL1 [, ...]]);
 *
 * For example:
 *
 *   var kl = new Array("LOAD_1MIN", "Load 1 Min",
 *                      "LOAD_5MIN", "Load 5 Min");
 *
 * We then generate a HTML table with a row for each KEY/LABEL pair which
 * we find a value for sysInfo[KEY] defined. If we don't find ANY defined
 * values, the empty string is returned.
 *
 * calcMaxValLen - if set to "true", the max charater count for the
 *                 largest value in "keyLabels" is returned... 
 *
 * Note: To determine the "value" with the max char width, the
 *       the global var: "keyLabelMaxDomTTWidth" will be zeroed out. */

function buildSysInfoTable(keyLabels, calcMaxValLen) {
  var html = "<table>";
  var cnt = 0;

  //
  // new table: zero out max char width value var...
  keyLabelMaxDomTTWidth = 0;

  for (var i = 0; i < keyLabels.length; i += 2) {
    var key = keyLabels[i];
    var label = keyLabels[i+1];
    if (sysInfo[key]) {
      cnt++;
      html += buildInfoTableRow(label, sysInfo[key], 'sys_info_' + key);
    }
  }

  //
  // return max char width value if true...
  if ((calcMaxValLen != null) && calcMaxValLen) {
    return keyLabelMaxDomTTWidth;
  }

  // If nothing found, return nothing
  if (cnt == 0) {
    return "";
  }

  html += "</table>";
  return html;
}

//
// Set dynamic tooltip (TT) for 'NST Distribution Release' info
// on this NST probe...

function nstDistoReleaseTT() {
  var nstDistoRelease = "<div class='line1px'>"
                      + "&#39;<span style='color: #33ff00;'>"
                      + "NST"
                      + "</span>&#39;"
                      + " Version Information</div>";

  // NOTE: Only the available keys will appear in the output table
  // (either WUI or HTML, but not both)
  var keyLabels = new Array(
    "NST_WUI_FULL_VERSION", "WUI Version",
    "NST_WUI_BUILD_DATE", "WUI Build Date",
    "NST_HTML_VERSION", "Web Site Version",
    "NST_HTML_BUILD_DATE", "Web Site Build Date",
    "NST_ISO_VERSION", "Distribution Version",
    "NST_ISO_BUILD_SVN_REVISION", "Distribution SVN",
    "NST_ISO_BUILD_DATE", "Distribution Build Date",
    "NST_ISO_DEV_VERSION", "Development Version");

  nstDistoRelease += buildSysInfoTable(keyLabels);

  return nstDistoRelease;
}

//
// Functions for a DOM node font-size increase/decrease...

//
// nodeIncreaseFontSize(nodeid,initFontSize,increment,max)
//
//        nodeid - DOM node id...
//  initFontSize - Optional Initial font-size (string) expressed as a percentage...
//     increment - Optional incremental percentage size (default: "10%")...
//           max - Optional maximum font size percentage value (default: "10000"%)...
function nodeIncreaseFontSize(nodeid,initFontSize,increment,max) {

  //
  // set initial vars...
  var node = document.getElementById(nodeid);

  //
  // adjust default font-size based on browser...
  var initfs = isIE() ? 80 : 100;
  var fsval = initfs;
  if (initFontSize != null) {
    if (initFontSize != "") {
      fsval = parseInt(initFontSize);
    }
  }

  var inc = 10;
  if (increment != null) {
    inc = increment;
  }
  var maxval = 10000;
  if (max != null) {
    maxval = max;
  }

  if (node) {
    if(node.style.fontSize) {
  //
  // get current font-size value and remove "%" or "inherit"...
      var curfs = node.style.fontSize.replace("%","");
      curfs = parseInt(curfs.replace("inherit",""));
      if (curfs != "") {
        fsval = parseInt(curfs);
      }
    }
  //
  // increment and range limit check...
    fsval = fsval + inc;
    if (fsval > maxval) {
      fsval = maxval;
    }
    node.style.fontSize = fsval + "%";
  }
}

//
// nodeDecreaseFontSize(nodeid,initFontSize,decrement,min)
//
//        nodeid - DOM node id...
//  initFontSize - Optional Initial font-size expressed as a percentage...
//     decrement - Optional decremental percentage size (default: "10%")...
//           max - Optional minimum font size percentage value (default: "10"%)...
function nodeDecreaseFontSize(nodeid,initFontSize,decrement,min) {

  //
  // set initial vars...
  var node = document.getElementById(nodeid);

  //
  // adjust default font-size based on browser...
  var initfs = isIE() ? 80 : 100;
  var fsval = initfs;
  if (initFontSize != null) {
    if (initFontSize != "") {
      fsval = parseInt(initFontSize);
    }
  }

  var decr = 10;
  if (decrement != null) {
    decr = decrement;
  }
  var minval = 1;
  if (min != null) {
    minval = min;
  }

  if (node) {
    if(node.style.fontSize) {
  //
  // get current font-size value and remove: "%" or "inherit" from results...
      var curfs = node.style.fontSize.replace("%","");
      curfs = parseInt(curfs.replace("inherit",""));
      if (curfs != "") {
        fsval = parseInt(curfs);
      }
    }
  //
  // decrement and range limit check...
    fsval = fsval - decr;
    if (fsval < minval) {
      fsval = minval;
    }

    node.style.fontSize = fsval + "%";
  }
}

/* Static class of helper function to build DOM parts.
 *
 * Refer to the methods defined below on what one can do with the NstDom
 * object.
 *
 * NOTE: There are MANY methods and variables under the NstDom namespace,
 * these are found in the core/NstDom*.js files. */

var NstDom = new function() {
  // Flag used to tell if we've been initialized or not
  this._Initialized = false;

  this._OrderedHeaders = [];
  this._HeaderMap = [];
  this._IdEnhancements = [];
  this._ExitUrl = false;
  this._ExitFunction = false;

  // Set to true if you need
  this._NeedExitAtEnd = false;

  // By default, don't add a exit area at the end of the document
  this._ExitAreaAtEnd = false;

  // Indicates that we have not yet computed the scroll bar width
  this._ScrollBarWidthComputed = false;

  // Associative array of tool tips
  this._ToolTips = new Array();
  this._ToolTipWidths = new Array();

  // Set of "onload" and "onunload" handlers
  this._OnloadHandlers = new Array();
  this._OnunloadHandlers = new Array();
};

//
// Folowing NstDom methods had to be relocated here (out of the core/NstDom.js
// file). These methods use @macros() which trigger JavaScript errors when
// loading the raw source file into browsers
//

/* NstDom.isProbeBuild()
 *
 * Returns true if site is built to be installed on a NST probe (false
 * indicates build is for main NST web site). */

NstDom.isProbeBuild = function() {
  return false;
}

/* Create a <img> element that user can click on to enable side navigation.
 *
 * pname - Optional name of the page to show (false if you want default).
 * url - Optional URL to open (if omitted we will try to "auto-detect"). */

NstDom.createEnableFramesIconBanner = function(pname, url) {
  var a = document.createElement("a");

  a.target = "_top";
  if (!url) {
    if (NstDom.isProbeBuild()) {
      url = "/nst/frame.cgi";
    } else {
      url = "/nst/frame.html";
    }
  }
  a.href = url;

  a.className = "navLinkA";
  a.onmouseover = function(event) {
    document.getElementById('domMenu_nav').style.display = 'block';
    NstDom.showEnableFramesTooltip(this, event, pname);
  }

  var img = document.createElement("img");
  img.className = "footerIcon";
  img.style.width = "24px";
  img.style.height = "24px";
  img.src = "/nst/images/contents.gif";
  //
  // Firefox  viewing '.xml' files needs tweaking...
  if (isFirefox()) {
    if (window.location.pathname.search(/\.xml$/) != -1) {
      img.style.marginBottom = "-6px";
    }
  }

  a.appendChild(img);
  return a;
}

/* Helper function for time delayed menu bar removal... */

NstDom.disableFramesIconBanner = function() {
  document.getElementById('domMenu_nav').style.display = 'none';
}
/**
 * NstIpList objects are used to manage lists of IP addresses.
 *
 * - Construct a new instance.
 * - Use the "add" methods to fill with data.
 * - Use the "contains" method to test whether a IP address is contained.
 *
 * Use the static createField method to create a DOM textarea node for
 * validated user input.
 */

function NstIpList() {
  this._IpList = [];
}

/**
 * Clears all of the IP addresses currently in the list.
 */

NstIpList.prototype.clear = function() {
  this._IpList = [];
}

/**
 * Adds a new IP address to the list of known addresses.
 *
 * ip - The IP address (like "192.168.1.2") to add to the list.
 *
 * return true if valid IP address passed and added successfully.
 */

NstIpList.prototype.add = function(ip) {
  if (NstIp.validateIp(ip)) {
    var idx = NstIp.parseInt(ip);
    this._IpList[idx] = true;
    return true;
  }
  return false;
}

/**
 * Test whether a string value contains a valid (parseable) list of IP
 * addresses.
 *
 * text - The text string to parse, like: "10.8.68.2 10.17.17.1"
 *
 * return true if text string contained a valid IP list.
 */

NstIpList.validateIpList = function(text) {
  var l = new NstIpList();
  return l.addList(text);
}

/**
 * Method used for field validation (to verify that data entered is valid).
 *
 * node - DOM node which you want to validate the "value" attribute of
 * (could be a <input> or <textarea> node.
 *
 * true If value contains a valid IP list, false if not.
 */

NstIpList.fieldValidator = function(node) {
  var isValid = NstIpList.validateIpList(node.value);
  NstDom.setValidationState(node, isValid);
  return isValid;
}

/**
 * Method used to create a validated input area for the user to enter
 * a IP list in.
 *
 * defVal - The default value for the list of IP addresses.
 * ttId - The Tool Tip ID to associate with the input field.
 * rows - The number or rows to display.
 * cols - The number of columns to display.
 *
 * return A DOM node that can be inserted into the document.
 */

NstIpList.createField = function(defVal, ttId, rows, cols) {
  var node = document.createElement("textarea");
  node.className = "NstInputFactory";

  if (typeof(defVal) != typeof("")) {
    defVal = "127.0.0.1";
  }

  // Save default value as old value and initial value
  node._Default = defVal;
  node.value = node._Old = defVal;

  // Set node validation method
  node._Validate = NstIpList.fieldValidator;

  // Set rows/cols
  node.rows = (rows ? rows : 3);
  node.cols = (cols ? cols : 132);

  // If DOM tooltip, then add onmouseover handler
  NstDom.setNodeToolTip(node, ttId);

  // Trap events that we want to perform a validation check on
  node.onkeyup = NstDom.checkValueChange;
  node.onblur = NstDom.checkValueChange;

  // Do initial validation of default value
  node._Validate(node);

  return node;
}

/**
 * Add a string containing a list of IP addresses to the collection.
 *
 * text - String of IP Address(es) separated by " ,\r\n" characters.
 *
 * return true If all parsed tokens were valid IP addresses, false if any
 * were not.
 */

NstIpList.prototype.addList = function(text) {
  var rc = true;
  var list = text.split(/[ \n\r,]+/);
  var n = list.length;
  for (var i = 0; i < n; i++) {
    var ip = list[i];
    // Ignore any empty string (might occur at start or end)
    if (ip.length == 0) {
      continue;
    }

    if (!this.add(ip)) {
      rc = false;
    }
  }
  return rc;
}

/**
 * Clears current contents and then adds a string containing a list of
 * IP addresses to the collection.
 *
 * text - String of IP Address(es) separated by " ,\r\n" characters.
 *
 * return true If all parsed tokens were valid IP addresses, false if any
 * were not.
 */

NstIpList.prototype.setList = function(text) {
  this.clear();
  return this.addList(text);
}

/**
 * Removes a IP address from the collection.
 *
 * ip The IP address to remove.
 */

NstIpList.prototype.remove = function(ip) {
  delete this._IpList[NstIp.parseInt(ip)];
}

/**
 * Checks to see if a integerized IP address value is contained in the
 * collection.
 *
 * ipIntVal The integerized IP address (see NstIp.parseInt() on
 * converting string IP values to integer values).
 */

NstIpList.prototype.containsInt = function(ipIntVal) {
  return (this._IpList[ipIntVal] == true);
}

/**
 * Checks to see if a IP address is contained in the collection.
 *
 * ip The IP address (like "10.8.68.2") to look for.
 */

NstIpList.prototype.contains = function(ip) {
  return this.containsInt(NstIp.parseInt(ip));
}

/** A "factory" class used to produce <fieldset> DOM objects.
 *
 * General usage:
 *
 * var fsf = new NstFieldSetFactory();
 * fsf.setLegend("Legend Text One");
 * var fset1 = fsf.createDomNode();
 * fsf.setLegend("Legend Text Two");
 * var fset2 = fsf.createDomNode();
 */

function NstFieldSetFactory() {
  this._Legend = null;
  this._ClassName = "NstFieldSetFactory";
}

/**
 * Set the text legend to use when creating a new fieldset object.
 *
 * legend - String to use for legend, pass null to disable legend. */

NstFieldSetFactory.prototype.setLegend = function(legend) {
  this._Legend = legend;
}

/**
 * Get the current text legend setting.
 *
 * return Text legend used when creating DOM nodes (null if legend disabled). */

NstFieldSetFactory.prototype.getLegend = function() {
  return this._Legend;
}

/**
 * Create a <fieldset> object - ready to insert into document.
 *
 * return DOM node reference to <fieldset> object created. */

NstFieldSetFactory.prototype.createDomNode = function() {
  var fset = document.createElement("fieldset");
  fset.className = this._ClassName;

  if (this._Legend) {
    var legend = document.createElement("legend");
    legend.className = this._ClassName;
    legend.appendChild(document.createTextNode(this._Legend));
    fset.appendChild(legend);
  }
  return fset;
}
/* Class to help work with IP addresses. */

function NstIp(ip) {
  this._Ip = ip;
}

/* Get the IP Address/Host Name associated with the object. */

NstIp.prototype.getIp = function() {
  return this._Ip;
}


/* Get URL to show IP address on map. */

NstIp.prototype.getMapUrl = function() {
  return "http://geo.flagfox.net/?ip=" + this.getIp();
}

/* Open a new window showing location of IP/Address on map. */

NstIp.prototype.showMapLocation = function() {
  window.open(this.getMapUrl(),"Host_Map",
              "height=620,width=820,toolbar=nodirectories=no,status=no,"
              + "menubar=no,scrollbars=yes,resizable=yes");
}

/* Display a DOM Tooltip for a link that will open the IP/Address on map.
 *
 * node - Node which triggered the event.
 * event - JavaScript event. */

NstIp.prototype.showMapToolTip = function(node, event) {

  var content = NstDom.ttNote("Show")
    + " the " + NstDom.ttEmphasis("Location", true)
    + " associated with:\n\n  "
  + NstDom.ttValue(this.getIp(), true);

  domTT_activate(node, event,
                 'content', content,
                 'width', 320);
}

/* Static method to automatically "hover enhance" a IP address.
 *
 * Assigns a tool tip and makes item "clickable" to show map. */

NstIp.hoverMapEnhance = function(node, event, ip) {
  if (!ip) {
    ip = node.firstChild.data;
  }
  // Update node
  node._NstIp = new NstIp(ip);
  node.onclick = function() {
    this._NstIp.showMapLocation();
  }

  node.onmouseover = function(event) {
    this._NstIp.showMapToolTip(this, event);
  }

  // Activate tooltip
  node._NstIp.showMapToolTip(node, event);
}

/*
 * Verify that a string contains a valid numeric IP address.
 *
 * val - String to be checked.
 *
 * Returns true if val has the form of D.D.D.D and each D
 * is a integer value in the range of 0-255 (like: "192.168.1.2").
 */

NstIp.validateIp = function(val) {
  if (typeof(val) != typeof("")) {
    return false;
  }

  // Verify we have the form X.X.X.X
  var parts = val.split(".");
  if (parts.length != 4) {
    return false;
  }

  // Verify the first 4 values are integers in the range of [0, 255]
  var constraints = {
    _MinVal: 0,
    _MaxVal: 255
  };
  for (var i = 0; i < 4; i++) {
    if (!NstDom.validateInt(parts[i], constraints)) {
      return false;
    }
  }

  // If we made it here, then everything is OK
  return true;
}

/*
 * Verify a DOM input node contains a valid IP address.
 *
 * node - DOM input node to validate.
 *
 * return true if user entered valid IP address.
 */

NstIp.ipFieldValidator = function(node) {
  var isValid = NstIp.validateIp(node.value, node);
  NstDom.setValidationState(node, isValid);
  return isValid;
}

/*
 * Create a text input field to enter a IP address in.
 *
 * defVal - Default (initial) value
 *
 * ttId - ID of registered DOM tooltip (null if you don't want it).
 *
 * len - length of input field visible (optional).
 */

NstIp.createIpInput = function(defVal, ttId, len) {
  var node = document.createElement("input");
  node.className = "NstInputFactory";

  if (typeof(defVal) != typeof("")) {
    defVal = "127.0.0.1";
  }

  // Save default value as old value and initial value
  node._Default = defVal;
  node.value = node._Old = defVal;

  // Set node validation method
  node._Validate = NstIp.ipFieldValidator;

  if (len) {
    node.length = len;
    node.style.width = "auto";
  }

  // At most 15 characters can be entered
  node.maxLength = 15;

  // If DOM tooltip, then add onmouseover handler
  NstDom.setNodeToolTip(node, ttId);

  // Trap events that we want to perform a validation check on
  node.onkeyup = NstDom.checkValueChange;
  node.onblur = NstDom.checkValueChange;

  // Do initial validation of default value
  node._Validate(node);

  return node;
}

/*
 * Verify that a string contains a valid host name as according to
 * WikiPedia which I think is based on RFC 952/1123: Maximum length
 * 255 chars, "." separated tokens (last token may be followed by a
 * trailing "."). There must be at least one token. Each token is 1-63
 * characters long containing only letters (case insensitive), digits
 * or the dash "-". A token may not start or end with a dash.
 *
 * According to RFC 1123, numeric IP addresses are valid host names
 * (RFC 952 apparently did not permit tokens to start with a digit but
 * RFC 1123 did).
 *
 * val - String to be checked.
 *
 * Returns true if string is a valid host name according to RFC 1123.
 */

NstIp.validateHostName = function(val) {
  if (typeof(val) != typeof("")) {
    return false;
  }

  // Exit now if string is too long
  if (val.length > 255) {
    return false;
  }

  // Verify we have at least one token
  var parts = val.split(".");
  if (parts.length < 1) {
    return false;
  }

  // Get number of tokens
  var n = parts.length;

  // If trailing "." at end of string, last token will be "", in this
  // case, back off the number of tokens to check by 1
  if (parts[n - 1] == "") {
    n -= 1;
    // Make sure we still have at least one token left
    if (n == 0) {
      return false;
    }
  }

  // Verify the first 4 values are integers in the range of [0, 255]
  var constraints = {
    _MinLen: 1,
    _MaxLen: 63,
    _RegExp: /^[a-zA-Z0-9]$|^[a-zA-Z0-9][-a-zA-Z0-9]*[a-zA-Z0-9]$/
  };

  for (var i = 0; i < n; i++) {
    if (!NstDom.validateText(parts[i], constraints)) {
      return false;
    }
  }

  // If we made it here, then everything is OK
  return true;
}

/*
 * Verify a DOM input node contains a valid host name address.
 *
 * node - DOM input node to validate.
 *
 * return true if user entered valid host name.
 */

NstIp.hostNameFieldValidator = function(node) {
  var isValid = NstIp.validateHostName(node.value, node);
  NstDom.setValidationState(node, isValid);
  return isValid;
}

/*
 * Create a text input field to enter a host name in.
 *
 * defVal - Default (initial) value
 *
 * ttId - ID of registered DOM tooltip (null if you don't want it).
 *
 * len - length of input field visible (optional).
 */

NstIp.createHostNameInput = function(defVal, ttId, len) {
  var node = document.createElement("input");
  node.className = "NstInputFactory";

  if (typeof(defVal) != typeof("")) {
    defVal = "www.networksecuritytoolkit.org";
  }

  // Save default value as old value and initial value
  node._Default = defVal;
  node.value = node._Old = defVal;

  // Set node validation method
  node._Validate = NstIp.hostNameFieldValidator;

  if (len) {
    node.length = len;
    node.style.width = "auto";
  }

  // At most 255 characters can be entered
  node.maxLength = 255;

  // If DOM tooltip, then add onmouseover handler
  NstDom.setNodeToolTip(node, ttId);

  // Trap events that we want to perform a validation check on
  node.onkeyup = NstDom.checkValueChange;
  node.onblur = NstDom.checkValueChange;

  // Do initial validation of default value
  node._Validate(node);

  return node;
}

/*
 * Verify that a string contains a valid host name OR numeric IP address.
 *
 * See NstIp.validateIp and NstIp.validateHostName for more details.
 *
 * val - String to be checked.
 *
 * Returns true if string is a valid.
 */

NstIp.validateIpOrHostName = function(val) {
  return (NstIp.validateIp(val) || NstIp.validateHostName(val));
}

/*
 * Verify a DOM input node contains a valid host name or IP address.
 *
 * node - DOM input node to validate.
 *
 * return true if user entered valid host name or IP address.
 */

NstIp.ipOrHostNameFieldValidator = function(node) {
  var isValid = NstIp.validateIpOrHostName(node.value, node);
  NstDom.setValidationState(node, isValid);
  return isValid;
}

/*
 * Create a text input field to enter a IP address or host name in.
 *
 * defVal - Default (initial) value
 *
 * ttId - ID of registered DOM tooltip (null if you don't want it).
 *
 * len - length of input field visible (optional).
 */

NstIp.createIpOrHostNameInput = function(defVal, ttId, len) {
  var node = document.createElement("input");
  node.className = "NstInputFactory";

  if (typeof(defVal) != typeof("")) {
    defVal = "www.networksecuritytoolkit.org";
  }

  // Save default value as old value and initial value
  node._Default = defVal;
  node.value = node._Old = defVal;

  // Set node validation method
  node._Validate = NstIp.ipOrHostNameFieldValidator;

  if (len) {
    node.length = len;
    node.style.width = "auto";
  }

  // At most 255 characters can be entered
  node.maxLength = 255;

  // If DOM tooltip, then add onmouseover handler
  NstDom.setNodeToolTip(node, ttId);

  // Trap events that we want to perform a validation check on
  node.onkeyup = NstDom.checkValueChange;
  node.onblur = NstDom.checkValueChange;

  // Do initial validation of default value
  node._Validate(node);

  return node;
}

/*
 * Create a <span> entity fo a IP address which the user can click
 * on for further investication.
 *
 * ipAddr - The IP address (or host name).
 * label - The label the user should see (if omitted, then the ipAddr is used).
 *
 * returns A DOM node ready for adding to the document.
 */

NstIp.createIpToolsLink = function(ipAddr, label) {
  if (!label) {
    // Use IP address as label (if no override)
    label = ipAddr;
  }
  var widget = document.createElement("span");
  widget.className = "ipToolsLink";
  widget.appendChild(document.createTextNode(label));
  widget._IpAddress = ipAddr;
  widget.onclick = function() {
    var url = "/nst/cgi-bin/wizards/ip.cgi?return=None&ip_addr="
      + escape(ipAddr);
    window.open(url, "_blank");
  }
  return widget;
}

/**
 * Gets the integer value associated with a IP address (mainly used
 * for sorting).
 *
 * a - IP address to parse.
 *
 * return Integer value of IP address.
 */

NstIp.parseInt = function(ip) {
  var parts = ip.split(".");
  if (parts.length < 4) {
    return 0;
  }
  var val = 0;
  for (var i = 0; i < 4; i++) {
    val *= 256;
    val += parseInt(parts[i]);
  }
  return val;
}

/**
 * Sort function that can be used to sort a array of IP addresses.
 *
 * a - IP address like 192.168.1.3
 * b - IP address to compare against a like 192.168.1.4
 *
 * return A negative value if a comes before b, a positive value if b
 * comes before a, or zero if equal.
 */

NstIp.sortIpFunction = function(a, b) {
  return NstIp.parseInt(a) - NstIp.parseInt(b);
}
/*
 * Classes related to building "choices" (radio buttons, check boxes, ...).
 */

/*
 * NstChoice
 *
 * Class representing a single choice. Each choice has:
 *
 * val - A value associated with the choice.
 * ttId - A optional registered tool tip ID to go with the selector.
 * show - A optional text label to show to the user.
 * ttIdShow - A optional registered tool tip ID to go with the the label.
 *
 * You typically only use individual NstChoice objects when building
 * a NstChoices collection.
 */

function NstChoice(val, ttId, show, ttIdShow) {
  this._Value = val;
  this._ToolTipId = ttId;
  if (typeof(show) == typeof(undefined)) {
    this._Show = val;
  } else {
    this._Show = show;
  }
  this._ShowToolTipId = ttIdShow;
}

/*
 * Returns the value associated with the choice.
 */

NstChoice.prototype.getValue = function() {
  return this._Value;
}

/*
 * Returns the text to show the user associated with the choice.
 */

NstChoice.prototype.getShow = function() {
  return this._Show;
}


/*
 * Create a named input field (typically "radio" or "checkbox").
 *
 * Unfortunately, IE is different from other browsers when creating
 * new radio button and checkbox input fields. This helper method
 * lets you create these types of objects without having to think
 * about the browser.
 *
 * name - The name attribute to assign to the <input> DOM node created.
 * type - The type attribute to assign to the <input> DOM node created.
 *
 * returns a DOM node of the specified type. */

NstChoice.createInput = function(name, type) {
  if (isIE()) {
    return document.createElement("<input name=\"" + name
				  + "\" type=\"" + type + "\"/>");
  }

  var node = document.createElement("input");
  node.setAttribute("type", type);
  node.name = name;
  return node;
}


/*
 * Create a labeled radio button to represent this choice.
 *
 * NOTE: You typically don't use this method directly, it is called
 * indirectly from the NstChoices class.
 *
 * gid - The group ID (name attribute) which the radio button belongs to.
 *
 * id - The unique ID for the radio button.
 *
 * cname - The class name to apply to the radio button and span used
 * for the label.
 * 
 * selected - Boolean (true or false) indicating whether the radio
 * button is selected.
 *
 * Returns a DOM node for insertion into document (a "div" containing
 * multiple other nodes).
 */

NstChoice.prototype.createRadioButton = function(gid, id, cname, selected) {
  // Create outer div for parts
  var div = document.createElement("div");
  div.className = cname;

  // Create and initialize the radio button
  var rb = NstChoice.createInput(gid, "radio");
  div.appendChild(rb);
  rb.id = id;
  rb.className = cname;
  rb.value = this._Value;
  rb.checked = selected;
  if (typeof(this._ToolTipId) == typeof("")) {
    NstDom.setNodeToolTip(rb, this._ToolTipId);
  }

  var rl = this.createLabel(cname);
  if (rl != null) {
    div.appendChild(rl);
  }

  return div;
}


/*
 * Create a check box to represent this choice.
 *
 * NOTE: You typically don't use this method directly, it is called
 * indirectly from the NstChoices class.
 *
 * gid - The group ID (name attribute) which the check box belongs to.
 *
 * id - The unique ID for the check box.
 *
 * cname - The class name to apply to the check box and span used
 * for the label.
 * 
 * selected - Boolean (true or false) indicating whether the check box
 * is initially checked.
 *
 * Returns a DOM node for insertion into document (a "div" containing
 * multiple other nodes).
 */

NstChoice.prototype.createCheckBox = function(gid, id, cname, selected) {
  // Create outer div for parts
  var div = document.createElement("div");
  div.className = cname;

  // Create and initialize the check box
  var rb = NstChoice.createInput(gid, "checkbox");
  div.appendChild(rb);
  rb.id = id;
  rb.className = cname;
  rb.value = this._Value;
  rb.checked = selected;
  if (typeof(this._ToolTipId) == typeof("")) {
    NstDom.setNodeToolTip(rb, this._ToolTipId);
  }

  var rl = this.createLabel(cname);
  if (rl != null) {
    div.appendChild(rl);
  }

  return div;
}

/*
 * Create a DOM representation of the "label" for this choice.
 *
 * return DOM node, OR null if label has been disabled. */

NstChoice.prototype.createLabel = function(cname) {
  // Create label (if available)
  if ((typeof(this._Show) == typeof("")) && (this._Show.length > 0)) {
    var rl = document.createElement("span");
    rl.className = cname;
    rl.appendChild(document.createTextNode(this._Show));
    if (typeof(this._ShowToolTipId) == typeof("")) {
      NstDom.setNodeToolTip(rl, this._ShowToolTipId);
    }
    return rl;
  }
  return null; // No label
}

/*
 * A collection of multiple NstChoice objects.
 *
 * This class is used to manage a group of choices. It is designed to
 * support both single and multiple selection choice types (radio buttons
 * or check boxes).
 *
 * choices - Array of NstChoice objects making up the set of available
 * choices.
 */

function NstChoices(choices) {
  this._Choices = choices;
  this._ClassName = "NstChoice";
  this._ChoicesPerRow = 10;
}


/*
 * Set the class name prefix to use when setting "class" attributes.
 *
 * By default, "NstChoice" is used as the base class name. This is
 * used in making the following assignemnts:
 *
 * NstChoiceMain - To the outer <div> containing all of the choices.
 * NstChoice - To the <div> containing a single choice.
 * NstChoice - To the <input> of each choice.
 * NstChoice - To the <span> of each choice label.
 *
 * cname - Pass something other than "NstChoice" if you would like to use
 * a custom set of styles for your chooser. */

NstChoices.prototype.setClassName = function(cname) {
  if (typeof(cname) == typeof(this._ClassName)) {
    this._ClassName = cname;
  }
}


/*
 * Set the maximum number of choices to display on a single row.
 *
 * If you have a lot of radio buttons or check boxes, you may want to
 * specify a value to indicate that a <br> be inserted after so many
 * items are installed (however in this case, maybe you should really
 * be asking your self if checkboxes or radio buttons are really
 * appropriate.
 *
 * cols - Maximum number of items per row.
 */

NstChoices.prototype.setChoicesPerRow = function(cnt) {
  if (cnt >= 1) {
    this._ChoicesPerRow = cnt;
  }
}


/*
 * Simple choice builder that takes a set of values and single tool tip ID.
 *
 * values - Array of choices user can pick from. Like: [ "TCP", "UDP", "ICMP" ]
 * ttid - Optional ID of registered tool tip used for all choices.
 */

NstChoices.createFromValues = function(values, ttid) {
  var choices = [];
  for (var i = 0; i < values.length; i++) {
    choices[i] = new NstChoice(values[i], ttid, values[i], ttid);
  }
  return new NstChoices(choices);
}

/*
 * A Boolean (true/false) chooser.
 *
 * tlabel - Label for the "true" value.
 * tttid - Tool tip ID for "true".
 * flabel - Label for the "false" value.
 * fttid - Tool tip ID for "false".
 */

NstChoices.createBoolean = function(tlabel, tttid, flabel, fttid) {
  var choices = [ new NstChoice("true", tttid, tlabel, tttid),
		  new NstChoice("false", fttid, flabel, fttid) ]
  return new NstChoices(choices);
}

/*
 * Helper method to compute the number of choices to display on each row.
 */

NstChoices.prototype.computeChoicesPerRow = function() {
  // Assume limit per row will be default value set in object
  var perRow = this._ChoicesPerRow;

  // Get minimum number of rows
  var nrows = (this._Choices.length + perRow - 1) / perRow;

  // If more than one row require, see if there are a lot of empty
  // slots in the last row and if so, reduce the columns per row
  // so the last row doesn't look so empty.
  if ((nrows > 1) && (perRow > 2)) {
    var emptySpots = perRow - (this._Choices.length % perRow);
    perRow -= (emptySpots / nrows);
  }    

  return perRow;
}

/*
 * Create a DOM node containing a set of radio buttons representing
 * the available choices.
 *
 * id - The unique ID to identify the entire group (all radio buttons
 * will have their name attribute set to this value). In addition, each
 * radio button will have a id of "id_IDX" where IDX ranges from [0, n-1]
 * according to the number of radio buttons.
 *
 * selectedIdx - The index of the radio button you want initially selected.
 */

NstChoices.prototype.createRadioButtons = function(id, selectedIdx) {
  var group = document.createElement("div");
  group.id = id;
  group.className = this._ClassName + "Main";


  var table = document.createElement("table");
  table.className = this._ClassName;
  group.appendChild(table);

  var tbody = document.createElement("tbody");
  tbody.className = this._ClassName;
  table.appendChild(tbody);

  var perRow = this.computeChoicesPerRow();

  var tr = null;

  for (var i = 0; i < this._Choices.length; i++) {
    // Add new row if first row or just reached end of last row
    if ((tr == null) || ((i % perRow) == 0)) {
      var tr = document.createElement("tr");
      tr.className = this._ClassName;
      tbody.appendChild(tr);
    }

    var choice = this._Choices[i];
    var rbNode = choice.createRadioButton(id, id + "_" + i,
					  this._ClassName, selectedIdx == i);
    var td = document.createElement("td");
    td.className = this._ClassName;
    td.appendChild(rbNode);
    tr.appendChild(td);
  }
  return group;
}

/*
 * Create a DOM node containing a set of radio buttons representing
 * the available choices.
 *
 * id - The unique ID to identify the entire group (all radio buttons
 * will have their name attribute set to this value). In addition, each
 * radio button will have a id of "id_IDX" where IDX ranges from [0, n-1]
 * according to the number of radio buttons. */

NstChoices.prototype.createCheckBoxes = function(id, selectedIdx) {
  var group = document.createElement("div");
  group.id = id;
  group.className = this._ClassName + "Main";

  var table = document.createElement("table");
  table.className = this._ClassName;
  group.appendChild(table);

  var tbody = document.createElement("tbody");
  tbody.className = this._ClassName;
  table.appendChild(tbody);

  var perRow = this.computeChoicesPerRow();

  var tr = null;

  for (var i = 0; i < this._Choices.length; i++) {
    // Add new row if first row or just reached end of last row
    if ((tr == null) || ((i % perRow) == 0)) {
      var tr = document.createElement("tr");
      tr.className = this._ClassName;
      tbody.appendChild(tr);
    }

    var choice = this._Choices[i];
    var rbNode = choice.createCheckBox(id, id + "_" + i,
				       this._ClassName, false);

    var td = document.createElement("td");
    td.className = this._ClassName;
    td.appendChild(rbNode);
    tr.appendChild(td);
  }
  return group;
}

/*
 * Create a DOM node containing a set of radio buttons representing
 * the available choices.
 *
 * id - The unique ID to identify the entire group (all radio buttons
 * will have their name attribute set to this value). In addition, each
 * radio button will have a id of "id_IDX" where IDX ranges from [0, n-1]
 * according to the number of radio buttons. */

NstChoices.prototype.createCheckBoxes = function(id, selectedIdx) {
  var group = document.createElement("div");
  group.id = id;
  group.className = this._ClassName + "Main";

  var table = document.createElement("table");
  table.className = this._ClassName;
  group.appendChild(table);

  var tbody = document.createElement("tbody");
  tbody.className = this._ClassName;
  table.appendChild(tbody);

  var perRow = this.computeChoicesPerRow();

  var tr = null;

  for (var i = 0; i < this._Choices.length; i++) {
    // Add new row if first row or just reached end of last row
    if ((tr == null) || ((i % perRow) == 0)) {
      var tr = document.createElement("tr");
      tr.className = this._ClassName;
      tbody.appendChild(tr);
    }

    var choice = this._Choices[i];
    var rbNode = choice.createCheckBox(id, id + "_" + i,
				       this._ClassName, false);

    var td = document.createElement("td");
    td.className = this._ClassName;
    td.appendChild(rbNode);
    tr.appendChild(td);
  }
  return group;
}

/*
 * Sets a event handler (like: "onclick") for ALL of the choices.
 *
 * id - The unique ID you used to create the GUI widget.
 *
 * ename - The name of the event handler you want to register a
 * callback function for (like: "onclick", "onblur", ...).
 *
 * efunc - The function you want invoked when the event occurs.
 */

NstChoices.prototype.setEventHandler = function(id, ename, efunc) {
  for (var i = 0; i < this._Choices.length; i++) {
    var nid = id + "_" + i;
    var node = document.getElementById(nid);
    if (node) {
      node[ename] = efunc;
    }
  }
}

/*
 * Used to look up the index of a single value.
 *
 * val - The value you want to find the index of.
 *
 * Returns the index in the range of [0, n-1] or -1 if not found.
 */

NstChoices.prototype.indexOf = function(val) {
  for (var i = 0; i < this._Choices.length; i++) {
    var choice = this._Choices[i];
    if (choice.getValue() == val) {
      return i;
    }
  }
  return -1;
}

/*
 * Returns a array of all selected choices (for radio button groups,
 * array will have one item selected).
 *
 * id - The ID of the DOM node you passed when created.
 */

NstChoices.prototype.getSelectedValues = function(id) {
  var values = [];

  for (var i = 0; i < this._Choices.length; i++) {
    var bid = id + "_" + i;
    var bnode = document.getElementById(bid);
    if (bnode && bnode.checked) {
      values.push(bnode.value);
    }
  }
  return values;
}

/*
 * Returns a array of all selected indexes (for radio button groups,
 * array will have one index).
 *
 * id - The ID of the DOM node you passed when created.
 */

NstChoices.prototype.getSelectedIndexes = function(id) {
  var values = [];

  for (var i = 0; i < this._Choices.length; i++) {
    var bid = id + "_" + i;
    var bnode = document.getElementById(bid);
    if (bnode && (bnode.checked || bnode.selected)) {
      values.push(i);
    }
  }
  return values;
}

/*
 * Returns the first selected value (which there should only be one
 * for radio button groups), or the defVal passed if nothing is
 * selected.
 *
 * id - The ID of the DOM node you passed when created.
 * defVal - The default value to return if nothing is selected.
 */

NstChoices.prototype.getSelectedValue = function(id, defVal) {
  var values = this.getSelectedValues(id);
  if (values.length >= 1) {
    return values[0];
  }
  return defVal;
}

/*
 * Returns the first selected index in the range of [0, n-1], or -1 if
 * nothing is selected.
 *
 * id - The ID of the DOM node you passed when created.
 */

NstChoices.prototype.getSelectedIndex = function(id) {
  var values = this.getSelectedIndexes(id);
  if (values.length >= 1) {
    return values[0];
  }
  return 0;
}

/*
 * Selects all of the GUI widgets of the items matching array of
 * string values passed. For example, assume all of your choices
 * were [ "eth0", "eth1", "eth2", "lo" ] if you pass a array of
 * vals of [ "eth0", "lo" ], then this method will "check" the
 * widgets associated with "eth0" and "lo" and uncheck the other
 * widgets in the group.
 *
 * id - The ID of the DOM node you passed when created.
 * The array of values that should be checked.
 */

NstChoices.prototype.setSelectedValues = function(id, vals) {
  var idxes = [];

  for (var i = 0; i < vals.length; i++) {
    var idx = this.indexOf(vals[i]);
    if (idx >= 0) {
      idxes.push(idx);
    }
  }
  this.setSelectedIndexes(id, idxes);
}

/*
 * Selects all of the GUI widgets having indexes matching the array of
 * indexes passed. For example, assume all of your choices
 * were [ "eth0", "eth1", "eth2", "lo" ] if you pass a array of
 * indexes of [ 0, 3 ], then this method will "check" the
 * widgets associated with "eth0" and "lo" and uncheck the other
 * widgets in the group.
 *
 * id - The ID of the DOM node you passed when created.
 * idxes - The array of indexes that should be checked.
 */

NstChoices.prototype.setSelectedIndexes = function(id, idxes) {
  idxes.sort();
  var uidx = 0;

  for (var si = 0; si < idxes.length; si++) {
    // index of next in list to check
    var sidx = idxes[si];

    // uncheck any preceding this since the last checking
    for (; uidx < sidx; uidx++) {
      var bid = id + "_" + uidx;
      var bnode = document.getElementById(bid);
      if (bnode) {
	if (this._IsSelect) {
	  bnode.selected = false;
	} else {
	  bnode.checked = false;
	}
      }
    }

    // Check selected index
    var bid = id + "_" + sidx;
    var bnode = document.getElementById(bid);
    if (bnode) {
      if (this._IsSelect) {
	bnode.selected = true;
      } else {
	bnode.checked = true;
      }
    }

    // Bump unselected index past one we just selected
    uidx = sidx + 1;
  }

  // Need to unselect any remaining items
  for (; uidx < this._Choices.length; uidx++) {
    var bid = id + "_" + uidx;
    var bnode = document.getElementById(bid);
    if (bnode) {
      if (this._IsSelect) {
	bnode.selected = false;
      } else {
	bnode.checked = false;
      }
    }
  }
}

/*
 * Select a single GUI widget of the items having a value matching the
 * value passed to this method. For example, assume all of your
 * choices were [ "eth0", "eth1", "eth2", "lo" ] if you pass a value
 * of "eth2", then this method will "check" the widget associated with
 * "eth2" and uncheck the other widgets in the group.
 *
 * id - The ID of the DOM node you passed when created.
 * val - The value that you would like checked.
 *
 * return true if "val" is valid and item selected, false if not.
 */

NstChoices.prototype.setSelectedValue = function(id, val) {

  var idx = this.indexOf(val);
  if (idx != -1) {
    this.setSelectedIndex(id, idx);
    return true;
  }

  return false;
}

/*
 * Select a single GUI widget of the items having a index matching the
 * value passed to this method. For example, assume all of your
 * choices were [ "eth0", "eth1", "eth2", "lo" ] if you pass a index
 * of 1, then this method will "check" the widget associated with
 * "eth1" and uncheck the other widgets in the group.
 *
 * id - The ID of the DOM node you passed when created.
 * idx - The index of the widget that you would like checked.
 */

NstChoices.prototype.setSelectedIndex = function(id, idx) {
  this.setSelectedIndexes(id, [ idx ]);
}

/*
 * Create a DOM node containing a <select> (pull down list) represention of
 * the available choices.
 *
 * id - The unique ID to identify the entire <select> node.
 *
 * selectedIdx - The index of the item you want initially selected.
 *
 * ttId - Tooltip ID for the select.
 */

NstChoices.prototype.createSelect = function(id, selectedIdx, ttId) {
  // Create <select>
  var s = document.createElement("select");
  s.id = id;
  s._IsSelect = true;
  s.className = this._ClassName;

  if (ttId) {
    NstDom.setNodeToolTip(s, ttId);
  }
  
  // Append <options>
  for (var i = 0; i < this._Choices.length; i++) {
    var choice = this._Choices[i];
    var o = document.createElement("option");
    o.id = id + "_" + i;
    o.value = choice.getValue();
    o.appendChild(document.createTextNode(choice.getShow()));
    s.appendChild(o);
  }

  // Set default selected item(s) and return DOM node
  s.selectedIndex = parseInt(selectedIdx);
  return s;
}

/**
 * Appends all radio buttons as "inline" elements.
 *
 * node - DOM node to append to.
 * id - ID of group.
 * selectedIdx - Index of item you want intially selected.
 */

NstChoices.prototype.appendRadioButtons = function(node, id, selectedIdx) {

  for (var i = 0; i < this._Choices.length; i++) {
    var choice = this._Choices[i];
    choice.appendRadioButton(node, id, id + "_" + i,
			     this._ClassName, selectedIdx == i);
  }
}

/**
 * Private method used to append all radio buttons as "inline" elements.
 *
 * node - DOM node to append to.
 * gid - ID of group.
 * id - ID of the radio button to create.
 * cname - Name of "class" to set on radio button created.
 * selectedIdx - Index of item you want intially selected.
 */

NstChoice.prototype.appendRadioButton = function(node, gid, id, cname, selected) {
  // Create and initialize the radio button
  var rb = NstChoice.createInput(gid, "radio");
  node.appendChild(rb);
  rb.id = id;
  rb.className = cname;
  rb.value = this._Value;
  rb.checked = selected;
  if (typeof(this._ToolTipId) == typeof("")) {
    NstDom.setNodeToolTip(rb, this._ToolTipId);
  }

  var rl = this.createLabel(cname);
  if (rl != null) {
    node.appendChild(rl);
  }
}


/** A "factory" class used to produce <button> DOM objects.
 *
 * General usage:
 *
 * var fsf = new NstButtonFactory();
 * fsf.setLabel("Press Me");
 * NstDom.addToolTip("ttPressMe", "Try pressing this button");
 * bf.setToolTipId("ttPressMe");
 * var b1 = bf.createDomNode();
 */

function NstButtonFactory() {
  this._Label = "Button";
  this._ClassName = "NstButtonFactory";
  this._ToolTipId = null;
  this._OnclickHander = null;
}

/**
 * Set the tooltip ID for the button.
 *
 * ttId - The ID of a tool tip previously registered via
 * a invocation of NstDom.addToolTip(), or null to disable. */
 
NstButtonFactory.prototype.setToolTipId = function(ttId) {
  this._ToolTipId = ttId;
}

/**
 * Set the label ID for the button.
 *
 * label - The text label to put on the button (or null if none). */

NstButtonFactory.prototype.setLabel = function(label) {
  this._Label = label;
}

/**
 * Set the function to call when the button is pressed.
 *
 * handler - The function to call when the button is pressed (or null
 * to disable). */

NstButtonFactory.prototype.setOnclickHandler = function(handler) {
  this._OnclickHandler = handler;
}

/**
 * Set the onclick handler to open a URL (possibly in a new window).
 *
 * url - The URL to open.
 *
 * winname - The window name to open in (null is same as "_self",
 * "_blank" for new window). */

NstButtonFactory.prototype.setOnclickUrl = function(url, winname) {
  var func = function() {
    var name = winname;
    if (!winname) {
      name = "_self";
    }
    window.open(url, name);
  }
  this.setOnclickHandler(func);
}

/**
 * Create a Properties object and initialize it with "return" settings.
 *
 * newWin - Set to "true" if you will be opening in a new window or tab
 * and want "Close" instead of "Return" buttons (defaults to false if omitted).
 * 
 * return Properties object used to build URL params. */

NstButtonFactory.prototype.getReturnProperties = function(newWin) {
  newWin = (newWin == true);

  var props = new Properties();
  if (newWin) {
    props.setValue("return", "None");
  } else {
    props.setValue("return", window.location.href);
    props.setValue("return_label", "Return");
    props.setValue("return_from", "Calling Page");
    try {
      props.setValue("return_from", document.getElementsByTagName("title")[0].text);
    } catch (e) { }
  }
  return props;
}

/**
 * Looks at the key/value pairs of the Properties object passed and
 * returns a "exit" button.
 *
 *    var bf = new NstButtonFactory();
 *    if (bf.setExitButton()) {
 *      var b = bf.createDomNode();
 *      // code to add exit button to document
 *    }
 */

NstButtonFactory.prototype.setExitButton = function(props) {
  NstDom.autoSetReturn();
  if (NstDom._NeedExitAtEnd) {

    // Prefer onclick function over URL (if available)
    if (NstDom._ExitFunction) {
      this.setOnclickHandler(NstDom._ExitFunction);
    } else {
      this.setOnclickUrl(NstDom._ExitUrl);
    }

    // Set label for button
    this.setLabel(NstDom._ExitLabel);

    // Register tooltip for exit button
    var ttId = "exit-button";
    if (!NstDom.getToolTip(ttId)) {
      NstDom.addToolTip(ttId, NstDom._ExitToolTip, NstDom._ExitToolTipWidth);
    }
    this.setToolTipId(ttId);

    return true;
  }

  // Don't need a exit button
  return false;
}

/**
 * Static method to create a Return/Exit button using defaults based on URL.
 *
 * return DOM <button> object if enough info in URL to create, null if not. */

NstButtonFactory.createExitButton = function() {
  var bf = new NstButtonFactory();
  if (bf.setExitButton(null)) {
    return bf.createDomNode();
  }
  return null;
}

/**
 * Static method to create a button which opens a URL when clicked.
 *
 * label - The text label to put on the button (or null if none).
 *
 * url - The URL to open.
 *
 * ttId - The ID of a tool tip previously registered via
 * a invocation of NstDom.addToolTip(), or null to disable.
 *
 * winname - The window name to open in (null is same as "_self",
 * "_blank" for new window).
 *
 * Returns DOM node ready for insertion into document. */

NstButtonFactory.createUrlButton = function(label, url, ttId, winname) {
  var bf = new NstButtonFactory();
  bf.setLabel(label);
  bf.setToolTipId(ttId);
  bf.setOnclickUrl(url, winname);
  return bf.createDomNode();
}

/**
 * Initialize the factory to generate a button to browse a directory.
 *
 * NOTE: This only works on the NST WUI!
 *
 * dname - The full path to the directory to browse to.
 *
 * newWin - Set to true if you want browser to open in new window/tab
 * with no return. */

NstButtonFactory.prototype.setDirBrowser = function(dname, newWin) {
  // Force to boolean
  newWin = (newWin == true);

  var url = "/nst/cgi-bin/system/browse.cgi";
  
  var props = this.getReturnProperties(newWin);
  props.setValue("path", dname);

  var ttId = "browse-" + dname;
  if (!NstDom.getToolTip(ttId)) {
    var tooltip = "<div class=\"line1px\">"
         + "<span style=\"color: #cc9900;\">"
         + "Browse"
         + "</span> directory:</div><div> &quot;"
         + "<span style=\"color: #00ccff; font-size: 12px; "
         + "font-weight: normal; font-family: Consolas, monospace;\">"
         + dname
         + "</span>&quot;</div>";
    var width = calcDomTTLen(dname, 20, 300, 1000);
    NstDom.addToolTip(ttId, tooltip, width);
  }

  this.setLabel("Browse");
  this.setToolTipId(ttId);
  this.setOnclickUrl(url + "?" + props.toUrlParams(),
		     (newWin ? "_blank" : "_self"));
}

/**
 * Initialize the factory to generate a ZIP download button.
 *
 * NOTE: This only works on the NST WUI!
 *
 * dname - The full path to the directory to ZIP up and download. */

NstButtonFactory.prototype.setDirDownload = function(dname) {
  var url = "/nst/cgi-bin/system/download.cgi";
  var props = this.getReturnProperties();
  props.setValue("path", dname);

  // Set download name to: SVN_NAME-Y-M-D.zip
  var d = new Date();
  var fname = basename(dname) + "-" + NstDom.formatIsoDate(d);
  props.setValue("name", fname);
  props.setValue("archive", "zip");

  var ttId = "download-zip-" + dname;
  if (!NstDom.getToolTip(ttId)) {
    var tooltip = "<div class=\"line1px\">"
         + "<span style=\"color: #cc9900;\">"
         + "Download ZIP"
         + "</span> file of directory:</div><div> &quot;"
         + "<span style=\"color: #00ccff; font-size: 12px; "
         + "font-weight: normal; font-family: Consolas, monospace;\">"
         + dname
         + "</span>&quot;</div>";
    var width = calcDomTTLen(dname, 20, 300, 1000);
    NstDom.addToolTip(ttId, tooltip, width);
  }

  this.setLabel("Download");
  this.setToolTipId(ttId);
  this.setOnclickUrl(url + "?" + props.toUrlParams());
}

/**
 * Create a <button> object - ready to insert into document.
 *
 * return DOM node reference to <button> object created. */

NstButtonFactory.prototype.createDomNode = function() {
  var node = document.createElement("button");
  node.className = this._ClassName;

  if (this._Label) {
    var label = document.createElement("span");
    label.className = this._ClassName;
    label.appendChild(document.createTextNode(this._Label));
    node.appendChild(label);
  }

  // If DOM tooltip, then add onmouseover handler
  if (this._ToolTipId) {
    node._ToolTipId = this._ToolTipId;
    var ttFunc = function(event) {
      NstDom.showToolTip(this, event, this._ToolTipId);
    };
    node.onmouseover = ttFunc;
  }

  // Add onclick handler (if set)
  if (this._OnclickHandler) {
    node.onclick = this._OnclickHandler;
  }

  return node;
}

/*
 * Create a <button> field with a associated tooltip ID and label.
 *
 * ttId - ID of registered DOM tooltip (omit if you don't want it).
 *
 * label - Label to appear on button (omit if you don't want).
 *
 * clickHandler - Call back function for "onclick" handler (omit if
 * you don't want).
 *
 * return - A <button> object.
 */

NstButtonFactory.createButton = function(ttId, label, clickHandler) {
  var bf = new NstButtonFactory();
  bf.setLabel(label);
  bf.setToolTipId(ttId);
  bf.setOnclickHandler(clickHandler);
  return bf.createDomNode();
}


/*
 * Create a <button> field with a associated tooltip ID and label.
 *
 * ttId - ID of registered DOM tooltip (omit if you don't want it).
 *
 * label - Label to appear on button (omit if you don't want).
 *
 * clickHandler - Call back function for "onclick" handler (omit if
 * you don't want).
 *
 * return - A <button> object.
 */

NstButtonFactory.createActionButton = function(ttId, label, clickHandler) {
  var b = NstButtonFactory.createButton(ttId, label, clickHandler);
  b.className = b.className + " ntrAction";
  return b;
}

/*
 * Creates a DOM "div" object to append buttons to when building a button bar.
 *
 * return DOM node to append buttons to. */

NstButtonFactory.createButtonBar = function() {
  var bbar = document.createElement("div");
  bbar.style.marginLeft = "auto";
  bbar.style.textAlign = "center";
  bbar.style.marginRight = "auto";
  return bbar;
}

/**
 * Changes the label of a previously created button.
 *
 * bnode - DOM node of button object previously created.
 * label - New label for node. */

NstButtonFactory.changeLabel = function(bnode, label) {
  var lnode = bnode.firstChild;
  lnode.replaceChild(document.createTextNode(label), lnode.firstChild);
}
/*
 * NstDom methods/variables related to validating user input.
 */

/*
 * Set global default for whether we validate fields as each character
 * is pressed or only when user presses enter or changes focus.
 */

NstDom.VALIDATE_ON_KEY = true;
try {
  if (_SESSION['validate_on_key']) {
    NstDom.VALIDATE_ON_KEY = (_SESSION['validate_on_key'] == "true");
  }
} catch (e) {
}

/*
 * Set value of a sanity checked input field (validates after setting value).
 * 
 * node - The DOM <input> node to set the new value on.
 * val - The new value for the input field.
 */

NstDom.setTextFieldValue = function(node, val) {
  // Update value displayed
  node.value = val;

  // If validation function registered, then validate new entry
  if (node._Validate) {
    node._Validate(node);
  }
}

/*
 * Method to validate that a value is a string meeting certain constraints.
 *
 * text - Value to check (at a minimum we will verify its a
 * string). You can pass null or omit (in which case we will return
 * false).
 *
 * constraints - Additional set of constraints defining your validation
 * checks. For example, the following would require the text to be 2
 * to 8 characters long containing only lower case letters:
 *
 *    { _MinLen: 2, _MaxLen: 8, _RegExp: /^[a-z]+$/ }
 *
 * The constraints argument is optional (you can omit or pass null if you
 * don't have any restrictions. All of the recognized attributes
 * within the constraints argument are also optional. For example, passing {
 * _MinLen: 1 } would just require that the text be at least one
 * character long.
 *
 * The following attributes are use if present within the constraints argument:
 *
 * _MinLen
 *  Verifies text is at least this many characters long.
 *
 * _MaxLen
 *  Verifies text is at most this many characters long.
 *
 * _RegExp

 *  A JavaScript regular expression. We will verify that if we search
 *  for the text in this regexp via text.search(constraints._RegExp) a
 *  non-negative value is returned. A word of caution, /^[a-z]+$/ is
 *  much different than /[a-z]+/, the former regexp accepts "abc" and
 *  rejects "1abc2" whereas the latter accepts both.
 *
 * returns true if text passes validation checks.
 */

NstDom.validateText = function(text, constraints) {
  // Verify its a text string
  if (typeof(text) != typeof("")) {
    return false;
  }

  // If no other additional checks, return true now
  if ((constraints == null) || (typeof(constraints) == typeof(undefined))) {
    return true;
  }

  var len = text.length;

  // If _MinLen limit present, then check value length
  var minLen = parseInt(constraints._MinLen);
  if (!isNaN(minLen) && (len < minLen)) {
    return false;
  }

  // If _MaxLen limit present, then check value length
  var maxLen = parseInt(constraints._MaxLen);
  if (!isNaN(maxLen) && (len > maxLen)) {
    return false;
  }

  // If regular expression check set, then verify search returns a value
  // greater than 0
  if (typeof(constraints._RegExp) != typeof(undefined)) {
    if (text.search(constraints._RegExp) < 0) {
      return false;
    }
  }

  return true;
}


/*
 * Method to validate that a value is a integer meeting certain constraints.
 *
 * val - The value to check (at a minimum we will verify its a integer
 * and perform a "string check" where we verify that if we convert the
 * integer value to a string we get the same value as the string value
 * of the original).
 *
 * constraints - Additional set of constraints defining your validation
 * checks. For example, the following would require the value to be within
 * the reange of 1 to 100:
 *
 *    { _MinVal: 1, _MaxVal: 100 }
 *
 * The constraints argument is optional (you can omit or pass null if you
 * don't have any constraints. All of the recognized attributes
 * within the constraints argument are also optional. For example, passing {
 * _MinVal: 10 } would just require that the integer be 10 or larger.
 *
 * The following attributes are use if present within the constraints argument:
 *
 * _MinVal
 *  Verifies integer value is at least this large.
 *
 * _MaxVal
 *  Verifies integer value is at most this large.
 *
 * _AllowBase
 *  Set to true if you want to permit the user to enter
 *  values like: "0x12" (base 16) or "010" (base 8). Note, if you
 *  permit this, we skip the "string check".
 *
 * returns true if text passes validation checks.
 */

NstDom.validateInt = function(val, constraints) {
  // Verify its a valid integer
  var ival = parseInt(val);
  if (isNaN(ival)) {
    return false;
  }

  // If no other additional checks, just verify that string value
  // of parsed integer matches string value of original
  if ((constraints == null) || (typeof(constraints) == typeof(undefined))) {
    return (ival.toString() == val.toString());
  }

  // If not permitting base conversions ("0x12", "010"), then
  // convert back to string value and fail if it doesn't match the original
  // string value.
  if ((constraints._AllowBase != true) && (ival.toString() != val.toString())) {
    return false;
  }

  // If _MinVal limit present, then check value against min
  var minVal = parseInt(constraints._MinVal);
  if (!isNaN(minVal) && (ival < minVal)) {
    return false;
  }

  // If _MaxVal limit present, then check value against max
  var maxVal = parseInt(constraints._MaxVal);
  if (!isNaN(maxVal) && (ival > maxVal)) {
    return false;
  }

  return true;
}

/*
 * Method to validate that a value is a floating point number meeting
 * certain constraints.
 *
 * val - The value to check (at a minimum we will verify its a floating
 * point number and perform a "string check" where we verify that if we
 * convert the float value to a string we get the same value as the string
 * value of the original).
 *
 * constraints - Additional set of constraints defining your validation
 * checks. For example, the following would require the value to be within
 * the reange of 0.0 to 1.0:
 *
 *    { _MinVal: 0.0, _MaxVal: 1.0 }
 *
 * The constraints argument is optional (you can omit or pass null if you
 * don't have any constraints. All of the recognized attributes
 * within the constraints argument are also optional. For example, passing {
 * _MinVal: 10.5 } would just require that the float be 10.5 or higher.
 *
 * The following attributes are use if present within the constraints argument:
 *
 * _MinVal
 *  Verifies the float value is at least this large.
 *
 * _MaxVal
 *  Verifies the float value is at most this large.
 *
 * returns true if text passes validation checks.
 */
NstDom.validateFloat = function(val, constraints) {

  // Make sure passed a string without any leading/trailing whitespace
  // and not empty
  if ((typeof(val) != typeof("")) ||
      (val.trim() != val) ||
      (val.trim() == "")) {
    return false;
  }

  // Convert string value to number (get back NaN if invalid
  // characters in or around number)
  var fval = (val * 1);
  if (isNaN(fval)) {
    return false;
  }

  //
  // If no other additional checks, just verify that string value
  // of parsed float matches string value of original...
  if ((constraints == null) || (typeof(constraints) == typeof(undefined))) {
    return true;
  }

  // If _MinVal limit present, then check value against min...
  var minVal = parseFloat(constraints._MinVal);
  if (!isNaN(minVal) && (fval < minVal)) {
    return false;
  }

  // If _MaxVal limit present, then check value against max...
  var maxVal = parseFloat(constraints._MaxVal);
  if (!isNaN(maxVal) && (fval > maxVal)) {
    return false;
  }

  return true;
}


/*
 * Invokes internal validation method of DOM node (if set).
 *
 * node - The DOM node to validate (we skip validation if not passed).
 *
 * Will only return false if passed a DOM node AND the DOM node has a
 * registered validation method (._Validate) AND the validation method
 * fails for the current state of the DOM node. This method returns
 * true in all other cases.
 */

NstDom.validateValue = function(node) {
  // If passed node with validation function
  if (node && (typeof(node._Validate) == typeof(NstDom.validateValue))) {
    return node._Validate(node);
  }

  // Validation not available, just assume its OK
  return true;
}

/*
 * Sets the ._IsVald attribute and adds/removes "valid" or "invalid" class.
 *
 * This method sets the ._IsValid attribute to true or false, and then
 * adjusts the .className attribute of the node passed. After invoking
 * this method, the .className attribute will also include either the
 * "valid" or "invalid" class.
 *
 * node - DOM node to adjust the validation state of.
 * isValid - Pass true if in valid state, false if not.
 */

NstDom.setValidationState = function(node, isValid) {
  
  if (isValid == true) {
    node._IsValid = true;
    NstDom.removeClass(node, "invalid");
    NstDom.addClass(node, "valid");
  } else {
    node._IsValid = false;
    NstDom.removeClass(node, "valid");
    NstDom.addClass(node, "invalid");
  }

}

/*
 * Method to validate the text field entered on a DOM input node.
 *
 * This method ends up invoking NstDom.validateText(node.value, node)
 * assuming that the validation properties (like _MinLen, _MaxLen,
 * _RegExp have been set on the node).
 *
 * Typically you don't interact with this method directly. See
 * NstDom.createTextInput and NstDom.validateText. It is typically set
 * as the _Validate attribute on a DOM <input> object.
 *
 * node - The DOM node (a <input> type) to validate the text field of.
 */

NstDom.textFieldValidator = function(node) {
  var isValid = NstDom.validateText(node.value, node);
  NstDom.setValidationState(node, isValid);
  return isValid;
}

/*
 * Method to validate a integer field entered on a DOM input node.
 *
 * This method ends up invoking NstDom.validateInt(node.value, node)
 * applying any of the constraint attributes set on the node (like:
 * _MinVal, _MaxVal, _AllowBase, ...).
 *
 * Typically you don't interact with this method directly. See
 * NstDom.createIntInput and NstDom.validateInt. It is typically set
 * as the _Validate attribute on a DOM <input> object.
 *
 * node - The DOM node (a <input> type) to validate the text field of.
 */

NstDom.intFieldValidator = function(node) {
  var isValid = NstDom.validateInt(node.value, node);
  NstDom.setValidationState(node, isValid);
  return isValid;
}

/*
 * Method to validate a float field entered on a DOM input node.
 *
 * This method ends up invoking NstDom.validateFloat(node.value, node)
 * applying any of the constraint attributes set on the node (like:
 * _MinVal, _MaxVal, _AllowBase, ...).
 *
 * Typically you don't interact with this method directly. See
 * NstDom.createFloatInput and NstDom.validateFloat. It is typically set
 * as the _Validate attribute on a DOM <input> object.
 *
 * node - The DOM node (a <input> type) to validate the text field of.
 */
NstDom.floatFieldValidator = function(node) {
  var isValid = NstDom.validateFloat(node.value, node);
  NstDom.setValidationState(node, isValid);
  return isValid;
}

/*
 * Callback method to register with "onkeyup" and "onblur" events.
 *
 * This callback method checks to see if the 'value' attribute has
 * changed from its old value (the _Old attribute on the DOM node). If
 * the value has been changed AND a _Validate method has been set for
 * the source node of the event, the validation method will be
 * invoked. This simplifies the process of creating input fields which
 * provide visual feedback to the user that they have entered a good
 * or bad value.
 *
 * You typically don't use this method directly - it supports the
 * NstDom.create...Input methods (like: NstDom.createTextInput,
 * NstDom.createIntInput, ...).
 *
 * event - DOM event (typically a keypup or onblur event).
 */

NstDom.checkValueChange = function(event) {
  if (!event) {
    event = window.event;
  }

  var e = event.target;
  if (!e) {
    e = event.srcElement;
  }

  // If validating after user finishes typing, skip validation unless
  // invoked because user pressed the Enter key
  if (NstDom.VALIDATE_ON_KEY == false) {
    if (((event.type == "keyup") || (event.type == "keypress")) &&
	(event.keyCode != 13)) {
      return;
    }
  }

  var val = e.value;
  if (typeof(val) != typeof(undefined)) {
    // Determine if value changed
    if (val != e._Old) {
      // If element has validator function attached, go invoke
      if (typeof(e._Validate) == typeof(NstDom.checkValueChange)) {
	e._Validate(e);
      }
      // Save current value as old to detect next change
      e._Old = e.value;
    }
  }
}


/*
 * Create a text input field for a text entry input with a associated
 * tooltip ID and optional checks.
 *
 * defVal - Default (initial) value
 *
 * minLen - Minimum length string user can enter (undefined if none)
 *
 * maxLen - Maximum length string user can enter (undefined if none).
 *
 * regExp - Optional regular expression used to validate text entry. A
 * "value.search(regExp)" test will be made and the search must result
 * in a value larger than or equal to 0 if the value entered by the
 * user is valid. For example: /^[a-z]{3,5}$/ would limit the field to
 * 3-5 lowercase alpha characters. Pass null if you don't want it.
 *
 * ttId - ID of registered DOM tooltip (null if you don't want it).
 *
 * len - length of input field visible (optional).
 */

NstDom.createTextInput = function(defVal, minLen, maxLen, regExp, ttId, len) {
  var node = document.createElement("input");
  node.className = "NstInputFactory";

  // Save default value as old value and initial value
  node._Default = defVal;
  node.value = node._Old = defVal;

  if (typeof(minLen) == typeof(1)) {
    node._MinLen = minLen;
  }

  if (typeof(maxLen) == typeof(1)) {
    node._MaxLen = maxLen;
    node.maxLength = maxLen;
  }

  if (regExp != null) {
    node._RegExp = regExp;
  }

  // Set node validation method
  node._Validate = NstDom.textFieldValidator;

  if (len) {
    node.length = len;
    node.style.width = "auto";
  }

  // If DOM tooltip, then add onmouseover handler
  NstDom.setNodeToolTip(node, ttId);

  // Trap events that we want to perform a validation check on
  node.onkeyup = NstDom.checkValueChange;
  node.onblur = NstDom.checkValueChange;

  // Do initial validation of default value
  node._Validate(node);

  return node;
}


/*
 * Create a text input field for integer only input with a associated
 * tooltip ID.
 *
 * defVal - Default value
 * minVal - Minimum value for field (undefined if none)
 * maxVal - Maximum value for field (undefined if none)
 * ttId - ID of registered DOM tooltip (null if you don't want it).
 * len - length of input field (optional).
 * maxlen - Maximum length of input field (optional).
 */

NstDom.createIntInput = function(defVal, minVal, maxVal, ttId, len, maxlen) {
  var node = NstDom.createTextInput(defVal, 1, maxlen, null, ttId, len);

  // Save any limit checks
  if (typeof(minVal) == typeof(1)) {
    node._MinVal = minVal;
  }

  if (typeof(maxVal) == typeof(1)) {
    node._MaxVal = maxVal;
  }

  // Set node validation method
  node._Validate = NstDom.intFieldValidator;

  // Do initial validation of default value
  node._Validate(node);

  return node;
}


/*
 * Create a text input field for float only input with a associated
 * tooltip ID.
 *
 * defVal - Default value
 * minVal - Minimum value for field (undefined if none)
 * maxVal - Maximum value for field (undefined if none)
 * ttId - ID of registered DOM tooltip (null if you don't want it).
 * len - length of input field (optional).
 * maxlen - Maximum length of input field (optional).
 */
NstDom.createFloatInput = function(defVal, minVal, maxVal, ttId, len, maxlen) {
  var node = NstDom.createTextInput(defVal, 1, maxlen, null, ttId, len);

  //
  // Save any limit checks...
  if (typeof(minVal) == typeof(1)) {
    node._MinVal = minVal;
  }

  if (typeof(maxVal) == typeof(1)) {
    node._MaxVal = maxVal;
  }

  //
  // Set node validation method...
  node._Validate = NstDom.floatFieldValidator;

  //
  // Do initial validation of default value...
  node._Validate(node);

  return node;
}


/*
 * Create a UUID input entry field following the "uuid -F STR" output format.
 *
 * defVal - Default (initial) value for field.
 * ttId - Tooltip ID.
 */

NstDom.createUuidInput = function(defVal, ttId) {
  // Verify form like: "6d07a35c-03a6-11e0-ad48-0800278124ba"
  var regexp = /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/;
  var _textInputNode = NstDom.createTextInput(defVal, 36, 36, regexp, ttId, 36);
  _textInputNode.style.width = '30em';
  return _textInputNode;
}

/*
 * Create a <tr> for building a two column input table.
 *
 * label - Label for left column of table.
 * node - DOM node that contains the "input" area.
 * isEven - true if "even" row, false if "odd" row.
 * thttid - Optional tooltip for table header
 *
 * returns A <tr> DOM node.
 */

NstDom.createInputRow = function(label, node, isEven, thttid) {
  var cname = "labeledRow";
  var tr = document.createElement("tr");

  var th = document.createElement("th");
  th.className = cname;
  th.align = "left";
  th.appendChild(document.createTextNode(label));
  if (thttid) {
    NstDom.setNodeToolTip(th, thttid);
  }
  tr.appendChild(th);

  var td = document.createElement("td");
  td.className = cname;
  td.appendChild(node);
  tr.appendChild(td);

  return tr;
}

/*
 * Verify that a string contains a valid email address according to
 * "most" of the validation rules found at:
 *
 *   http://en.wikipedia.org/wiki/E-mail_address
 *
 * Here are the basics from the URL above:
 *
 *    local-partnetworksecuritytoolkit.org  (jane.doe@gmail.com)
 *
 * local-part (jane.doe):
 *   - Up to 64 characters long
 *   - There is a set of restricted characters which may appear
 *   - The period can not appear at the start, end or multiple times in a row
 *   - Technically can be quoted, "local-part" to allow use of non-standard
 *     characters, but we don't support this (at least not initially)
 *
 * domain (gmail.com):
 *   - Typically host name form (not sure if "com" would be a legal domain)
 *   - Can be a IP address if enclosed in square brackets [73.32.12.199] is
 *     apparently legal.
 *
 * val - String to be checked.
 *
 * Returns true if val has the form of local-partnetworksecuritytoolkit.org or local-part@[IP].
 */

NstDom.validateEmail = function(val) {
  if (typeof(val) != typeof("")) {
    return false;
  }

  // Make sure there are two parts separated by a "@" symbol
  var parts = val.split("@");
  if (parts.length != 2) {
    return false;
  }

  var lpart = parts[0];
  var domain = parts[1];

  // The local part of the address can be 1-64 characters long
  if ((lpart.length == 0) || (lpart.length > 64)) {
    return false;
  }

  // domain should be a valid host name, OR a IP address within square brackets
  if (!NstIp.validateHostName(domain)) {
    // Quick checks for on [IP]
    if ((domain.length < 9) ||
        (domain.charAt(0) != "[") || 
        (domain.charAt(domain.length - 1) != "]")) {
      return false;
    }
    if (!NstIp.validateIp(domain.substring(1, -1))) {
      return false;
    }
  }

  // At this point, domain is OK, need to validate the local-part of email
  var specialOk = false;
  var normalChars = "0123456789abcdefghijklmnopqrstuvwxyz"
    + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    + "!#$%&'*+-/=?^_`{|}~";
  var specialChars = normalChars + ".";

  var normalChars
  for (var i = 0; i < lpart.length; i++) {
    var c = lpart.charAt(i);

    // If a normal character, reset special flag and move to next char
    if (normalChars.indexOf(c) >= 0) {
      specialOk = true;
      continue;
    }

    // If OK to have special character and is special character, then
    // clear special flag and move to next character
    if (specialOk && (c == '.')) {
      specialOk = false;
      continue;
    }

    // Found a invalid character, or a special character when not permitted
    return false;
  }
  // specialOk will be true if last character was not a "special" character
  return specialOk;
}

/*
 * Verify a DOM input node contains a valid email address.
 *
 * node - DOM input node to validate.
 *
 * return true if user entered valid email address.
 */

NstDom.emailFieldValidator = function(node) {
  var isValid = NstDom.validateEmail(node.value, node);
  NstDom.setValidationState(node, isValid);
  return isValid;
}

/*
 * Create a email input entry field using the validation rules found at:
 *
 *   http://en.wikipedia.org/wiki/E-mail_address
 *
 * defVal - Default (initial) value for field.
 * ttId - Tooltip ID.
 * len - length of input field visible (optional).
 */

NstDom.createEmailInput = function(defVal, ttId, len) {
  var node = document.createElement("input");
  node.className = "NstInputFactory";

  if (typeof(defVal) != typeof("")) {
    defVal = "";
  }

  // Save default value as old value and initial value
  node._Default = defVal;
  node.value = node._Old = defVal;

  // Set node validation method
  node._Validate = NstDom.emailFieldValidator;

  if (len) {
    node.length = len;
  }

  // At most 64 (local-part) + 1 (@ symbol) + 255 (domain/hostname) characters
  node.maxLength = (64 + 1 + 255);

  // If DOM tooltip, then add onmouseover handler
  NstDom.setNodeToolTip(node, ttId);

  // Trap events that we want to perform a validation check on
  node.onkeyup = NstDom.checkValueChange;
  node.onblur = NstDom.checkValueChange;

  // Do initial validation of default value
  node._Validate(node);

  return node;
}

/**
 * Creates a input field allowing user to set a color RGB value.
 *
 * NOTE: In order to get a full color chooser, you MUST include the
 * javascript/jscolor/jscolor.js file in the <head> section of your page.
 *
 * defval: The default RGB value (6 hex digits like: "ff8040").
 * ttid: Optional tool tip ID to associate with the input field.
 *
 * return A DOM node you can add to the page which the user can interact with.
 */

NstDom.createColorField = function(defval, ttid) {
  var fld = document.createElement("input");
  fld.length = fld.maxlength = 6;
  fld.value = (defval ? defval : "000000");
  fld.className = "jscolor";
  if (ttid) {
    NstDom.setNodeToolTip(fld, ttid);
  }
  if (jscolor) {
    fld.color = jscolor.color(fld, {});
  }
  return fld;
}
/*
 * NstDOM functions relocated from core.js.
 */

/* Automatically set the "return" features for the page based on URL params.
 *
 * Parses the search string from the URL associated with the page
 * looking for "return", "return_label" and "return_from" values which
 * will automatically configure how the page is rendered by adding
 * additional features when these values are present. */

NstDom.autoSetReturn = function() {
  if (NstDom._AutoReturnDone) {
    return;
  }
  var props = new Properties();
  props.fromUrlParams(window.location.search);

  var url = NstDom.getSubmitValue("return", false);
  url = props.getValue("return", url);

  var label = NstDom._ExitLabel;
  if (!label) {
    label = NstDom.getSubmitValue("return_label", "Return");
    label = props.getValue("return_label", label);
  }

  // If title not set explicitly, set it now
  var title = NstDom._ExitTitle;
  if (!title) {
    title = "Calling Page";
    title = NstDom.getSubmitValue("return_from", title);
    title = props.getValue("return_from", title);
  }

  var tooltip;
  var tooltipWidth;

  //
  // Figure out some reasonable defaults for the tool tip based
  // on URL and Label values
  //
  if (url == "None") {
    url = "javascript:window.close();";
    label = "Close";
    tooltip = NstDom.ttAction("Close") + " this "
      + NstDom.ttEmphasis("Tab") + "/" + NstDom.ttEmphasis("Window") + "...";
    tooltipWidth = 190;
  } else if (label == "Exit") {
    tooltip = NstDom.ttAction(label) + " from this page...";
    tooltipWidth = calcDomTTLen(17 + label.length, -20);
  } else {
    tooltip = NstDom.ttAction(label) + " to page: "
      + NstDom.ttEmphasis(title, true) + "...";
    tooltipWidth = calcDomTTLen(15 + label.length + title.length, -40);
  }

  // Check for tooltip overrides
  tooltip = NstDom.getSubmitValue("return_tooltip", tooltip);
  tooltipWidth = NstDom.getSubmitValue("return_tooltip_width", tooltipWidth);
  tooltip = props.getValue("return_tooltip", tooltip);
  tooltipWidth = props.getValue("return_tooltip_width", tooltipWidth);

  // Transfer values for later use
  this._ExitUrl = url;
  this._ExitLabel = label;
  this._ExitToolTip = tooltip;
  this._ExitToolTipWidth = tooltipWidth;

  this._NeedExitAtEnd = (this._ExitUrl != false);
  NstDom._AutoReturnDone = true;
}

/* Automatically append a return/exit button to end of node specified.
 *
 * If the NstDom.autoSetReturn() method was invoked earlier and
 * determined that the page has a exit location, this method can be
 * used to append a exit button to the DOM node passed (typically
 * document.body).
 *
 * node - The DOM node to append the exit button to.
 * sep - Pass true if you want a horizontal separator included before 
 * inserting the exit button. */

NstDom.autoAppendReturn = function(node, sep) {
  // Make sure exit button needed and able to create
  if (node && this._NeedExitAtEnd && this._ExitUrl) {
    var button = NstButtonFactory.createExitButton();
    if (button) {

      // If they want a separator, include it now
      if (sep) {
        var header = document.createElement("h1");
        header.id = "autoSetReturn";
        header.className = "bodyHeader";
        node.appendChild(header);
        NstDom.registerSection(header.id);
      }

      var bbar = document.createElement("div");
      bbar.style.marginLeft = "auto";
      bbar.style.textAlign = "center";
      bbar.style.marginRight = "auto";
      bbar.style.marginTop = ".5em";
      bbar.style.marginBottom = ".5em";
      node.appendChild(bbar);
      bbar.appendChild(button);
    }
  }
}

/* NstDom.activate()
 *
 *   Currently, we require one to activate the NstDom singleton
 *   before any of "registered" features will be enabled.  This is due
 *   to the fact that this code is included by MANY different pages
 *   which may not be compatible with the NST framework.  NOTE: It
 *   does not hurt to invoke this method multiple times (all
 *   subsequent invocations are ignored).
 *
 *   NOTE: Initialization will take control of some of the window
 *   events (like "onload" and "onunload").
 */

NstDom.activate = function() {
  if (NstDom._Initialized) {
     return NstDom;
  }
  NstDom._Initialized = true;

  // Add onmouseover for registered tooltips
  NstDom.registerOnloadHandler(NstDom.idEnhance);
  NstDom.registerOnloadHandler(NstDom.headerEnhance);
  NstDom.registerOnloadHandler(NstDom.spanEnhance);

  // Guarantee scroll to 'hash' window.location for IE Browsers...
  if (isIE()) {
    NstDom.registerOnloadHandler(NstDom.scrollToHash);
  }

  window.onload = NstDom.runAndClearOnloadHandlers;
  window.onunload = NstDom.runAndClearOnunloadHandlers;

  return NstDom;
}

/* NstDom.doNothing()
 *
 *   Function which does nothing. Useful to disable existing functions. */

NstDom.doNothing = function() {
}

/* NstDom.disableSpanEnhance()
 *
 *   Disables the enhancment of spans which is enabled when
 *   one invokes "activate". */

NstDom.disableSpanEnhance = function() {
  NstDom.spanEnhance = NstDom.doNothing;
}

/* NstDom.disableHeaderEnhance()
 *
 *   Disables the enhancment of headers which is enabled when
 *   one invokes "activate". */

NstDom.disableHeaderEnhance = function() {
  NstDom.headerEnhance = NstDom.doNothing;
}

/* NstDom.addToolTip(id, DOM_TOOLTIP) 
*
*  Add a new tooltip to the collection.
*
*  id - ID of tooltip to add (nice if it matches ID of HTML element as well)
*  tooltip - String or full DOM entity to show as tooltip.
*  width - Optional width parameter. */

NstDom.addToolTip = function(id, tooltip, width) {
  NstDom._ToolTips[id] = tooltip;
  if (width) {
    NstDom._ToolTipWidths[id] = width;
  }
}

/* NstDom.getToolTip(id)
 *
 * Get tooltip associated with a particular ID which was previously added.
 *
 * id - ID of tooltip
 * 
 * return String or DOM node, or undefined. */

NstDom.getToolTip = function(id) {
    return NstDom._ToolTips[id];
}

/* NstDom.registerOnloadHandler(f)
 *
 * Registers a "onload" handler function "f" which will be invoked AFTER the
 * page is loaded. */

NstDom.registerOnloadHandler = function(f) {
  NstDom._OnloadHandlers.push(f);
}

/* NstDom.registerOnunloadHandler(f)
 *
 * Registers a "onunload" handler function "f" which will be invoked when the 
 * page is unloaded. */

NstDom.registerOnunloadHandler = function(f) {
  NstDom._OnunloadHandlers.push(f);
}

/*
 * ===== BEGIN PRIVATE NstDom METHODS - YOU SHOULD NOT USE! =====
 */

/* NstDom.runAndClearOnloadHandlers()
 *
 * Runs all of the registered "onload" handlers AFTER the page has
 * loaded. You do not call this method directly (it is invoked
 * automatically). */

NstDom.runAndClearOnloadHandlers = function() {

    for (var idx in NstDom._OnloadHandlers) {
	var handler = NstDom._OnloadHandlers[idx];
        //        try {
          handler();
        /*        } catch (e) {
          alert("Failed to run handler[" + idx + "]\n\n" + handler);
          } */
    }

    // Set any registered tooltips
    NstDom.registerToolTips();

    NstDom._OnloadHandlers = new Array();
}

/* NstDom.runAndClearOnunloadHandlers()
 *
 * Runs all of the registered "onload" handlers AFTER the page has
 * loaded. You do not call this method directly (it is invoked
 * automatically). */

NstDom.runAndClearOnunloadHandlers = function() {

    for (var idx in NstDom._OnunloadHandlers) {
	var handler = NstDom._OnunloadHandlers[idx];
	handler();
    }

    NstDom._OnunloadHandlers = new Array();
}

/* NstDom.showToolTip(node[, event[, ttid])
 *
 * Private method (you should not need to invoke this directly), which
 * handles the "onmouseover" event for elements affected by the 
 * NstDom.registerToolTips() invocation.
 *
 * node - DOM element to show tooltip for.
 *
 * event - Window event (or null if IE).
 *
 * ttid - Optional ID of tooltip (if omitted, we will use the
 * "_ToolTipId" attribute from the node if available or the nodes "id"
 * if not). */

NstDom.showToolTip = function(node, event, ttid) {

    if (event == null) {
	event = window.event;
    }

    // If tooltip id not specified, use ID of element
    if (ttid == null) {
      if (node._ToolTipId) {
        ttid = node._ToolTipId;
      } else {
	ttid = node.id;
      }
    }

    var tt = NstDom.getToolTip(ttid);
    if (tt) {
      var width = 300;
      if (NstDom._ToolTipWidths[ttid]) {
        width = NstDom._ToolTipWidths[ttid];
      } else {
        width = 300;
	if (tt.length > 30) {
	  width = 460;
	}
      }

      // Show tool tip ID if debug level is high enough
      if (NstConsole.debugLevel(1)) {
	tt = tt + "<div class=\"line1px\"><br /></div>"
	  + "Tooltip ID: " + NstDom.ttEmphasis(ttid);
      }

      domTT_activate(node, event, 'content', tt, 'width', width);
    } else if (ttid != "disable") {
      domTT_activate(node, event, 'content', 
                     'No tooltip registered with ID: '
                     + NstDom.ttValue(ttid, true), 'width', 320);
    }

}

/* NstDom.setNodeToolTip(node, ttId[, tt[, width]])
 *
 *   Sets the onmouseover event for a particular node to display 
 *   a DOM tooltip.
 *
 * node - DOM node (or id of node) to associate tooltip with.
 * ttId - Id of tooltip to lookup.
 * tt - Optional text of tooltip (if it wasn't previously registered
 * via NstDom.addToolTip(ttId, tt)).
 * width - Optional width (if providing body of tooltip). */

NstDom.setNodeToolTip = function(n, ttId, tt, width) {
  // If tooltip ID not passed, don't do anything
  if ((typeof(ttId) != typeof(""))) {
    return;
  }

  // If body of tooltip provided, go add it to table
  if (tt) {
    NstDom.addToolTip(ttId, tt, width);
  }

  // Now get node and set onmouse over function to display tooltip
  var node = NstDom.getNode(n);
  if (node) {
    var showNodeToolTip = function(event) {
      NstDom.showToolTip(node, event, ttId);
    }
    node.onmouseover = showNodeToolTip;
  }
}


/* NstDom.registerToolTips()
 *
 * Private method (you should not need to invoke this directly), which
 * adds tool tip handlers to all of the elements on the page which have a "id"
 * attribute which matches one of the "id"s that was previously registered
 * via the "addToolTip" function. NOTE: Does NOT replace onmouseover handler
 * if one was already installed for the element. */

NstDom.registerToolTips = function() {

    for (var id in NstDom._ToolTips) {
	var node = document.getElementById(id);
	if (node && !node.onmouseover) {
	    
	    node.onmouseover = function(e) {
		NstDom.showToolTip(this, e);
	    }
	}
    }

    // Enable DOM tooltips now
    domTT_documentLoaded = true;

}

/*
 * ===== END PRIVATE NstDom METHODS - YOU SHOULD NOT USE! =====
 */

/* NstDom.showExitToolTip(node, event, label, title)
 *
 * Show DOM tooltip for exit button.
 *
 * node - The node (button).
 * event - The event triggering the need to show.
 * label - The label of the button (or '' if unknown).
 * title - The title to appear (or '' if unknown). */

NstDom.showExitToolTip = function(node, event, label, title) {
  // Initialize exit tooltip information
  if (label) {
    NstDom._ExitLabel = label;
  }
  if (title) {
    NstDom._ExitTitle = title;
  }
  NstDom.autoSetReturn();

  // Display tool tip
  domTT_activate(node, event, 'content', this._ExitToolTip,
		 'width', this._ExitToolTipWidth);

}

/* NstDom.getSubmitValue(key, def)
 *
 * If the JavaScript _SUBMIT[] array exists and contains a entry
 * for the specified 'key', the value associated with the entry
 * will be returned. Otherwise the default value is returned.
 *
 * NOTE: Typially CGI is used to build the _SUBMIT[] array.
 *
 * key - The 'key' name to use in lookup.
 * def - The default value to return if nothing is assocated with 'key'. */

NstDom.getSubmitValue = function(key, def) {
  if (window._SUBMIT && (typeof _SUBMIT[key] != typeof undefined)) {
    return _SUBMIT[key];
  }
  return def;
}

/* NstDom.showExitToolTipAdvanced(event)
 *
 *  */

NstDom.showExitToolTipAdvanced = function(node, event) {
  var tt = NstDom.getSubmitValue('return_tooltip', false);
  // Check for any POST/GET overrides
  if (tt) {
    var width = NstDom.getSubmitValue('return_tooltip_width', 300);
    
    domTT_activate(node, event, 'content', tt, 'width', width);
  } else {
    var label = NstDom.getSubmitValue('return_label', '');
    var title = NstDom.getSubmitValue('return_from', '');
    NstDom.showExitToolTip(node, event, label, title);
  }
}

/* NstDom.performExitAction()
 *
 * What to do when a "Exit/Return" button is pressed. */

NstDom.performExitAction = function(event) {
  var url = NstDom.getSubmitValue("return", NstDom._ExitUrl);
  if (NstDom._ExitFunction) {
    NstDom._ExitFunction();
  } else if (url) {
    window.location = url;
  } else {
    alert("Error in page - no Exit handler/URL set.");
  }
}

/* Builds a DOM node containing a single Exit/Return button.
 *
 * The resulting button behavior/appearance is controlled by:
 *
 *   NstDom.setExitUrl()
 *   NstDom.setExitFunction()
 *
 * If the _SUBMIT[] array is present, any of the following
 * entries will override the default behavior:
 *
 *   _SUBMIT['return']
 *     Exit/return URL to jump to on button press.
 *
 *   _SUBMIT['return_label']
 *     Label to appear on the button.
 *
 *   _SUBMIT['return_tooltip']
 *     Full DOM tooltip to use for button.
 *
 *   _SUBMIT['return_tooltip_width']
 *     Width for full tooltip (only used if 'return_tooltip' set).
 *
 *   _SUBMIT['return_from']
 *     Name of page to return to (only used when building
 *     tooltip when _SUBMIT['return_tooltip'] is omitted.
 *
 * Finally, if any of the return values are set in the URL itself,
 * they will override the _SUBMIT values.
 */

NstDom.createExitButton = function() {
  NstDom.autoSetReturn();

  // Create/initialize the new button
  var ebutton = document.createElement("button");
  ebutton.className = "wuiInputButton";
  ebutton.onmouseover = function(event) {
    NstDom.showExitToolTipAdvanced(this, event);
  }
  ebutton.onclick = NstDom.performExitAction;

  // Determine and set the label
  var label = NstDom._ExitLabel;
  ebutton.appendChild(document.createTextNode(label));

  return ebutton;
}

/* NstDom.replaceWithExitButton(node)
 *
 * 
 */

NstDom.replaceWithExitButton = function(node) {
  NstDom.autoSetReturn();

  // If we have a return location, use it
  if (NstDom._NeedExitAtEnd) {
    var ebutton = NstDom.createExitButton();
    node.parentNode.replaceChild(ebutton, node);
  } else {
    node.parentNode.removeChild(node);
  }
}

/* NstDom.replaceWithExitButtonRow(node)
 *
 * 
 */

NstDom.replaceWithExitButtonRow = function(node) {
  NstDom.autoSetReturn();

  var cblock = document.createElement("center");
  cblock.id = node.id;

  // If we have a return or exit location, insert button
  if (NstDom._NeedExitAtEnd) {
    // Create a centered button
    cblock.className = "exitButtonRow";
    var ebutton = NstDom.createExitButton();
    cblock.appendChild(ebutton);
  } else {
    // Just update the 
    cblock.className = "exitButtonRowNoButton";
    cblock.appendChild(document.createTextNode("\u00a0"));
  }

  // Replace existing node
  node.parentNode.replaceChild(cblock, node);
}

/* NstDom.getComputedStyle(node, cssStyleName)
 *
 *   Attempts to determine the computed style for a particular DOM node.
 *
 * node - The DOM node to get style of.
 * cssStyleName - The name of the CSS style to look up (ex: "font-size").
 * def - What to return if not found.
 *
 * Returns style value (like: "2px") or empty string if unable to determine.
 *
 * Based on "Robert's Talk" code example found at: 
 *
 * http://www.robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/
 */

NstDom.getComputedStyle = function(node, cssStyleName, def) {

  if (document.defaultView && document.defaultView.getComputedStyle) {
    var styleInfo = document.defaultView.getComputedStyle(node, "");
    var styleVal = styleInfo.getPropertyValue(cssStyleName);
    return (styleVal ? styleVal : def);
  }

  if (node.currentStyle){
    var strCssRule = cssStyleName.replace(/\-(\w)/g,
                                          function (strMatch, p1){
                                            return p1.toUpperCase();
                                          });
    return (node.currentStyle[strCssRule] ? node.currentStyle[strCssRule] : def);
  }
  
  return def;
}

/* Convert string ID, DOM node or null to a DOM node.
 *
 * node - ASCII ID of node, null or DOM node.
 *
 * returns DOM node or null. */

NstDom.getNode = function(node) {
  if (!node) {
    return null;
  }
  if (typeof node == "string") {
    return document.getElementById(node);
  }
  return node;
}


/* Scroll to the specified DOM node into view...
 *
 * node - DOM node (or its ASCII ID) to scroll to bottom of.
 *
 * topbottom - Parameter to the JavaScript function: "scrollIntoView"
 *             Set it to "true" or "false", indicating whether the top (true)
 *             of the element is scrolled to the top of the window or the
 *             bottom (false) of the element is scrolled to the
 *             bottom of the window. */

NstDom.scrollTo = function(node, topbottom) {

  //
  // Get the DOM node to scroll into view...
  node = NstDom.getNode(node);
  if (!node) {
    return;
  }

  //
  // Try to scroll desired node into view...
  node.scrollIntoView(topbottom);
}

/* Scroll to the bottom of a specified DOM node.
 *
 * node - DOM node (or its ASCII ID) to scroll to bottom of.
 *
 * firefoxCorrect - Size (in pixels) to adjust for decoration(s) (I
 * think this is bottom margin) at the bottom of the widget.
 *
 * reserve - Size (in pixels) to reserve at the bottom. */

NstDom.scrollToBottom = function(node, ffc, reserve) {

  // Get the DOM node to scroll
  node = NstDom.getNode(node);
  if (!node) {
    return;
  }

  // Get view port dimensions
  var vsize = NstDom.getViewportSize();

  // Start with enough to scroll top of node to top of viewport
  var scrollAmount = node.offsetTop;
  // Move down enough to put bottom edge of node just off screen
  scrollAmount += node.offsetHeight;
  // Move up the size of the viewport
  scrollAmount -= vsize[1];

  // Firefox treats decorations differently than IE when scrolling
  if (!isIE()) {
    scrollAmount += ffc;
  }

  // Move back down enough for any reserved space at the bottom
  scrollAmount += (reserve ? reserve : 0);
  
  window.scrollTo(0, scrollAmount);
}


/* Scroll to the current location set by the 'hash' window.location
 * property.
 *
 * top - Pass true (or omit) to goto top, false to goto bottom.
 *
 * **Note: The scroll will only take place if there is a
 *         matching 'node id' on the page that has the
 *         same name as the 'hash' value. */

NstDom.scrollToHash = function(top) {
  // Force top to true if not explicitly set to true
  top = (top != false);

  NstDom.scrollTo(window.location.hash.replace(/#/,''), top);
}


/* Select a range of text within a DOM node. The text will be
 * highlighted...
 * 
 * node  - DOM node (or its ASCII ID). This is
 *         typically an text input field.
 *
 * start - Position of start of text (numerical value).
 * end   - Position of end of text (numerical value).
 *
 * Returns: Returns "true" if successful otherwise "false". */

NstDom.setSelectionRange = function(node, start, end) {

  //
  // Get the DOM node for range selection...
  node = NstDom.getNode(node);
  if (!node) {
    return false;
  }

  //
  // first set cursor insertion point node focus...
  node.focus();

  //
  // typically a gecko browser (i.e. firefox)...
  if (node.setSelectionRange) {
    node.setSelectionRange(start, end);
    return true;

  //
  // typically the IE browser...
  } else if (node.createTextRange) {
    var range = node.createTextRange();
    range.collapse(true);
    range.moveStart("character", start);
    range.moveEnd("character", end - start);
    range.select();
    return true;
  } else {
    return false;
  }
}


/* Get the start position of the selected (highlighted) text
 * within a DOM node.
 *
 * node  - DOM node (or its ASCII ID). This is
 *         typically an text input field.
 *
 * Returns: The index position of the start of the selection
 *          range. Returns "-1" if an invalid node is passed.
 *          Returns "-2" if no method is found for determining
 *          the start position. */

NstDom.getSelectionStart = function(node) {

  //
  // Get the DOM node for start of selection value...
  node = NstDom.getNode(node);
  if (!node) {
    return -1;
  }

  //
  // typically a gecko browser (i.e. firefox)...
  if (node.selectionStart) {
    return node.selectionStart;

  //
  // typically the IE browser...
  } else if (node.createTextRange) {
    var range = node.createTextRange();
    var isCollapsed = range.compareEndPoints("StartToEnd", range) == 0;
    if (! isCollapsed) {

  //
  // move insertion point to beginning of text...
      range.collapse(true);
    }
    var b = range.getBookmark();
    return b.charCodeAt(2) - 2;
  } else {
    return -2;
  }
}


/* Get the end position of the selected (highlighted) text
 * within a DOM node.
 *
 * node  - DOM node (or its ASCII ID). This is
 *         typically an text input field.
 *
 * Returns: The index position of the end of the selection
 *          range. Returns "-1" if an invalid node is passed.
 *          Returns "-2" if no method is found for determining
 *          the end position. */

NstDom.getSelectionEnd = function(node) {

  //
  // Get the DOM node for end of selection value...
  node = NstDom.getNode(node);
  if (!node) {
    return -1;
  }

  //
  // typically a gecko browser (i.e. firefox)...
  if (node.selectionEnd) {
    return node.selectionEnd;

  //
  // typically the IE browser...
  } else if (node.createTextRange) {
  var range = node.createTextRange();
  var isCollapsed = range.compareEndPoints("StartToEnd", range) == 0;
  if (! isCollapsed) {

  //
  // move insertion point to end of text...
    range.collapse(false);
  }
	var b = range.getBookmark();
	return b.charCodeAt(2) - 2;
  } else {
    return -2;
  }
}


/* Set the cursor insertion point focus within a text field
 * at the "end" of the text. IE puts the cursor focus at the
 * begining of the text field while Firefox puts it at the end.
 *
 * node  - DOM node (or its ASCII ID). This is
 *         typically an text input field.
 *
 * Returns: Returns "true" if successful otherwise "false". */

NstDom.setCursorFocusEnd = function(node) {

  //
  // Get the DOM node...
  node = NstDom.getNode(node);
  if (!node) {
    return false;
  }

  //
  // if not the IE browser...
  if (! isIE()) {
    node.focus();
    return true;
  } else {
  //
  // get the end position of text within the field...
    var endpos = NstDom.getSelectionEnd(node);
    if (endpos >= 0) {
      var rs = NstDom.setSelectionRange(node, endpos, endpos);
      return rs;
    } else {
      return false;
    }
  }
}


/* Set the cursor insertion point focus within a text field
 * at the "start" of the text.
 *
 * node  - DOM node (or its ASCII ID). This is
 *         typically an textarea field.
 *
 * Returns: Returns "true" if successful otherwise "false". */

NstDom.setCursorFocusStart = function(node) {

  //
  // Get the DOM node...
  node = NstDom.getNode(node);
  if (!node) {
    return false;
  }

  // 
  // Set node to have focus
  node.focus();

  //
  // Firefox requires us to set position to start
  if (isNetscape()) {
    NstDom.setSelectionRange(node, 0, 0);
  }

  return true;
}



/* Returns current vertical scroll offset (or -1 if unable to determine). */

NstDom.getScrollY = function() {
  if (document.body.scrollTop) {
    return document.body.scrollTop;
  }  
  if (document.documentElement && document.documentElement.scrollTop) {
    return document.documentElement.scrollTop;
  }
  // Don't know how to get at this point, OR all above returned 0
  return 0;
}

/* Returns current horizontal scroll offset. */

NstDom.getScrollX = function() {
  if (document.body.scrollLeft) {
    return document.body.scrollLeft;
  }  
  if (document.documentElement && document.documentElement.scrollLeft) {
    return document.documentElement.scrollLeft;
  }
  // Don't know how to get at this point, OR all above returned 0
  return 0;
}

/* NstDom.getScrollBarWidth()
 *
 * Returns the width of the vertical scroll bar in pixels (if possible). */

NstDom.getScrollBarWidth = function() {
  // Only compute one time

/*

  if (this._ScrollBarWidthComputed) {
    if (NstDom.debugLevel(8, true)) {
      NstConsole.echo("Estimated Scrollbar Width: " + this._ScrollBarWidth);
    }
    return this._ScrollBarWidth;
  }
  this._ScrollBarWidthComputed = true;
*/

  /*
   * Paul's method (2008-04-25):
   *
   * <table>
   *  <tbody>
   *   <tr>
   *     <td> // block with fixed width height
   *      <div> // div with overflow set to scroll
   *       <div> // inner div
   *      
   * - Display everything off the visible area
   * - Scroll bar width is width of outer most <td> minus
   *   width of innermost div. */

  // Create table and position off page
  var table = document.createElement('table');
  table.style.position = 'absolute';
  table.style.left = '-1000px';
  table.style.top = '-1000px';

  var tbody = document.createElement('tbody');
  table.appendChild(tbody);

  var tr = document.createElement('tr');
  tbody.appendChild(tr);

  var td = document.createElement('td');
  tr.appendChild(td);
  td.style.margin = '0px';
  td.style.padding = '0px';
  td.style.border = 'none';

  var scroll = document.createElement('div');
  td.appendChild(scroll);
  scroll.style.margin = '0px';
  scroll.style.padding = '0px';
  scroll.style.border = 'none';
  scroll.style.overflow = 'scroll';

  var inner = document.createElement('div');
  scroll.appendChild(inner);
  inner.style.margin = '0px';
  inner.style.padding = '0px';
  inner.style.border = 'none';
  inner.style.height = "100px";
  inner.style.width = "100px";

  document.body.appendChild(table);

  // Get width of vertical scroll bar
  if (NstDom.debugLevel(9, true)) {
    NstConsole.echo("td Offset Width: " + td.offsetWidth);
    NstConsole.echo("inner Offset Width: " + inner.offsetWidth);
  }
  this._ScrollBarWidth = (td.offsetWidth - inner.offsetWidth);
  //
  // check for 0 scroll bar width - found Google Chrome (Beta)...
  if (this._ScrollBarWidth == 0) {
    this._ScrollBarWidth = 17;
  }

  // Get height of horizontal scroll bar
  if (NstDom.debugLevel(9, true)) {
    NstConsole.echo("td Offset Height: " + td.offsetHeight);
    NstConsole.echo("inner Offset Height: " + inner.offsetHeight);
  }
  this._ScrollBarHeight = (td.offsetHeight - inner.offsetHeight);

  document.body.removeChild(table);

  return this._ScrollBarWidth;
}

/* NstDom.getScrollBarHeight()
 *
 * Returns the width of the vertical scroll bar in pixels (if possible). */

NstDom.getScrollBarHeight = function() {
  // The NstDom.getScrollBarWidth method does the actual measurement
  if (!this._ScrollBarWidthComputed) {
    NstDom.getScrollBarWidth();
  }
  return this._ScrollBarHeight;
}

/** Try to determine whether or not a horizontal scroll bar is displayed
 * for a particular node.
 *
 * node - DOM node to check.
 *
 * returns true if shown, false if not. */

NstDom.isHorizontalScrollBarShown = function(node) {
  var mode = NstDom.getComputedStyle(node, "overflow-x", "unknown");
  if (mode == "scroll") {
    return true;
  }
  if ((mode == "auto") && (node.offsetWidth < node.scrollWidth)) {
    return true;
  }
  return false;
}

/** Try to determine whether or not a vertical scroll bar is displayed
 * for a particular node.
 *
 * node - DOM node to check.
 *
 * returns true if shown, false if not. */

NstDom.isVerticalScrollBarShown = function(node) {
  var mode = NstDom.getComputedStyle(node, "overflow-y", "unknown");
  if (mode == "scroll") {
    return true;
  }
  if ((mode == "auto") && (node.offsetHeight < node.scrollHeight)) {
    return true;
  }
  return false;
}

/* NstDom.debugLevel(level, andVisible)
 *
 * This method will return true, IF AND ONLY IF:
 *
 * - The NstConsole is available (JavaScript loaded).
 * - The NstConsole is shown (if andVisible == true).
 * - The NstConsole debug level is at the level specifed.
 *
 * level - Debug level to test (for example, if you pass 4, then
 * true will only be returned if the NstConsole debug level is set
 * to 4 or higher).
 *
 * andVisible - If true, then false will ALWAYS be returned when
 * the NstConsole is not currently active. */

NstDom.debugLevel = function(level, andVisible) {

  if (window.NstConsole) {
    if (NstConsole.debugLevel(level)) {
      return ((andVisible == false) || NstConsole.isVisible());
    }
  }
  return false;

}

/* NstDom.getViewportSize()
 *
 * If able to determine the viewable area of the screen, this method
 * returns an array containing two elements [ width_pixels, height_pixels ].
 *
 * This code is a slightly modified version of what was presents at:
 * http://ecmascript.stchur.com/2006/09/.
 *
 * @return Array containing width and height (in pixels) - or null if unable
 * to determine. */

NstDom.getViewportSize = function() {
  var size = null; // Assume unable to determine

  // First try to get info from the 'window' object
  if (window.innerWidth) {
    size = [ window.innerWidth, window.innerHeight ];

  // Next, try getting info from 'document.documentElement'
  } else if (document.documentElement &&
	     document.documentElement.clientWidth) {
    size = [ document.documentElement.clientWidth,
	     document.documentElement.clientHeight ];
  } else {
    // Finally, fall back to checking clientWidth/clientHeight of <body>
    var body = document.getElementsByTagName('body')[0];
    if (body && body.clientWidth) {
      size = [ body.clientWidth, body.clientHeight ];
    }
  }

  if (size && NstDom.debugLevel(9, true)) {
    NstConsole.echo("Current viewport width: " + size[0] + "  height:" + size[1]);
  }

  return size;    
}


/* NstDom.setViewportHeight(node, fixedHeight, decorHeight)
 *
 *   Used to set the height of a DOM node such that it fits in
 *   nicely within the user's scroll area.
 *
 * node - DOM node to set the size of.
 * 
 * fixedHeight - Fixed amount to subtract off (in pixels). Should
 * be the size of any other vertically placed nodes which will appear.
 *
 * decorHeight - Height (in pixels) of any decoration spacing (margin,
 * border, padding) in the node's style. */

NstDom.setViewportHeight = function(node, fixedHeight, decorHeight) {
  var vsize = NstDom.getViewportSize();
  var h = vsize[1];
  h -= (fixedHeight + decorHeight);
  node.style.height = h + "px";

  if (NstDom.debugLevel(6, true)) {
    var id = "node";
    if (node.id) {
      id = node.id;
    }
    NstConsole.echo(id + " adjusted height: " + h + "px");
  }

}

/* NstDom.setViewportWidth(node, fixedWidth, decorWidth)
 *
 *   Used to set the width of a DOM node such that it fits in
 *   nicely within the user's scroll area.
 *
 * node - DOM node to set the size of.
 * 
 * fixedWidth - Fixed amount to subtract off (in pixels). Should
 * be the size of any other horizontally placed nodes which will appear.
 *
 * decorWidth - Width (in pixels) of any decoration spacing (margin,
 * border, padding) in the node's style. */

NstDom.setViewportWidth = function(node, fixedWidth, decorWidth) {
  var vsize = NstDom.getViewportSize();
  var w = vsize[0];
  if (isIE()) {
    // Prior to new DOCTYPE
    // w -= (fixedWidth);
    w -= (fixedWidth + decorWidth);
  } else {
    // Firefox requires that we backoff the decoration and scroll bar width
    w -= (fixedWidth + decorWidth + NstDom.getScrollBarWidth());
  }
  node.style.width = w + "px";

  if (NstDom.debugLevel(6, true)) {
    var id = "node";
    if (node.id) {
      id = node.id;
    }
    NstConsole.echo(id + " adjusted width: " + w + "px");
  }
}

/* NstDom.setExitUrl(url)
 *
 *   Set URL to associate with the "exit" location (enables "EXIT" icon on 
 *   header bars). This is only used if a exit function was not set. */

NstDom.setExitUrl = function(url) {
  this._ExitUrl = url;
}

/* NstDom.setExitFunction(url)
 *
 *   Set function to associate with the "exit" action (enables "EXIT" icon on 
 *   header bars). This overrides any exit URL which was set. */

NstDom.setExitFunction = function(f) {
  this._ExitFunction = f;
}


/* NstDom.getTextContent(node)
 *
 *   Verifies that DOM node contains a single #TEXT entity and returns
 *   the text of the #TEXT entity as a string (or null if not found). */

NstDom.getTextContent = function(node) {
    if (node && (node.childNodes.length == 1) && (node.firstChild.data)) {
	return node.firstChild.data;
    }
    return null;
}

/* NstDom.registerSection(id)
 *
 * id - ID of header (must not be null). */

NstDom.registerSection = function(id) {
  NstDom._HeaderMap[id] = NstDom._OrderedHeaders.length;
  NstDom._OrderedHeaders.push(id);
}

/* NstDom.ttAcronym(acronym) - Tooltip format a acronym. */

NstDom.ttAcronym = function(acronym) {
  return "<span class=\"ttAcronym\">" + acronym + "</span>";
}

/* NstDom.ttAction(action[, quote])
 *
 * Tooltip format a action (pass true as 2nd parameter to have it quoted). */

NstDom.ttAction = function(action, quote) {
  var tt = quote ? "'" : "";
  tt += "<span class=\"ttAction\">" + action + "</span>";
  if (quote) {
    tt += "'";
  }
  return tt;
}

/* NstDom.ttCommand(command[, quote])
 *
 * Tooltip format a command (pass true as 2nd parameter to have it quoted). */

NstDom.ttCommand = function(command, after_colon) {
  var tt = after_colon ? "'" : "";
  tt += '<span class="ttCommand">' + command + '</span>';
  if (after_colon) {
    tt += "'";
  }
  return tt;
}

/* NstDom.ttEmphasis(text[, quote])
 *
 * Tooltip format for emphasis (pass true as 2nd parameter to have it quoted). */

NstDom.ttEmphasis = function(text, sq) {
  var tt = sq ? "'" : "";
  tt += '<span class="ttEmphasis">' + text + '</span>';
  if (sq) {
    tt += "'";
  }
  return tt;
}

/* NstDom.ttNav(page)
 *
 * Tooltip format a reference to another page or section in WUI. */

NstDom.ttNav = function(page) {
  return "'<span class=\"ttNav\">" + page + "</span>'";
}

/* NstDom.ttNote(note[, quote][, underline])
 *
 * Tooltip format a note (pass true as 2nd parameter to have it quoted). */

NstDom.ttNote = function(text, sq, ul) {
  var tt = sq ? "'" : "";
  if (ul) {
    tt += '<u><span class="ttNote">' + text + '</span></u>';
  } else {
    tt += '<span class="ttNote">' + text + '</span>';
  }
  if (sq) {
    tt += "'";
  }
  return tt;
}

/* NstDom.ttNormal(text[, quote])
 *
 * Tooltip format for normal text (pass true as 2nd parameter to have
 * it quoted). */

NstDom.ttNormal = function(text, sq) {
  var tt = sq ? "'" : "";
  tt += '<span class="ttNormal">' + text + '</span>';
  if (sq) {
    tt += "'";
  }
  return tt;
}

/* NstDom.ttNST()
 *
 * Tooltip format of NST acronym. */

NstDom.ttNST = function() {
  return NstDom.ttAcronym("NST");
}

/* NstDom.ttSite(site_or_url)
 *
 * Tooltip format a web site name or URL. */

NstDom.ttSite = function(site_or_url) {
  return '"<span class="ttSite">' + site_or_url + '</span>"';
}

/* NstDom.ttValue(value[, quote])
 *
 * Tooltip format a value (pass true as 2nd parameter to have it quoted). */

NstDom.ttValue = function(value, quote) {
  var tt = (quote ? '"' : "");
  tt += '<span class="ttValue">' + value + '</span>';
  if (quote) {
      tt += '"';
  }
  return tt;
}

/* NstDom.reloadSection(node)
 *
 * Searches for parent node whose ID was previously registered via
 * NstDom.registerSection(). Then attempts to reload the page and position
 * it at the specified position. */

NstDom.reloadSection = function(node) {
  var hidx = NstDom.getSectionNode(node);

  if ((hidx === undefined) || (hidx < 0)) {
    positionReload("#top");
  } else {
    // Move to location
    positionReload("#" + NstDom._OrderedHeaders[hidx]);
  }

}

/* NstDom.getSectionNode(node) 
 * 
 *   Searches for registered section node starting from 'node'. Checks
 *   'node' and all parent nodes until a node with a ID matching one
 *   of the registered sections is found.
 * 
 * node - The DOM node to start from (or ASCII string of ID of DOM
 * node to start from, or omit if 'this' works as the DOM node). */

NstDom.getSectionNode = function(node) {
  // If passed string (id attribute), look up node
  if (typeof node == "string") {
    node = document.getElementById(node);
  }

  // If 'node' not known, try to fall back to 'this' (if it looks like
  // a DOM node)
  if (!node) {
    if (this.parentNode) {
      node = this;
    } else {
      return undefined;
    }
  }

  for (; node != document; node = node.parentNode) {
    var hidx = NstDom._HeaderMap[node.id];
    if (hidx != undefined) {
      return hidx;
    }
  }

  return undefined;
}

/* NstDom.moveToPreviousSection(node)
 *
 * Searches for parent node whose ID was previously registered via
 * NstDom.registerSection(). Then jumps to section which was registered
 * just prior (or top of document if none). */

NstDom.moveToPreviousSection = function(node) {
  var hidx = NstDom.getSectionNode(node);

  if ((hidx === undefined) || (hidx <= 0)) {
    window.location = "#top";
    return;
  }

  // Move to location
  window.location = "#" + NstDom._OrderedHeaders[hidx - 1];
}

/* NstDom.moveToNextSection(node)
 *
 * Searches for parent node whose ID was previously registered via
 * NstDom.registerSection(). Then jumps to section which was registered
 * just after (or bottom of document if none). */

NstDom.moveToNextSection = function(node) {
  var hidx = NstDom.getSectionNode(node);

  var lastIndex = NstDom._OrderedHeaders.length - 1;
  if ((hidx === undefined) || (hidx >= lastIndex)) {
    window.location = "#bottom";
    return;
  }

  // Move to location
  window.location = "#" + NstDom._OrderedHeaders[hidx + 1];
}

/* Create a <img> element that user can click on to toggle the open/close
 * state (hide/show) another element on the page. 
 *
 * nodeId - ID of other element to toggle the state of. NOTE: If the nodeId
 * does not exist, then this method will return null. */

NstDom.createOpenCloseIcon = function(nodeId) {
  var node = document.getElementById(nodeId);
  if (!node) {
      return null;
  }

  // Build image icon and append to <div>
  var img = document.createElement("img");
  img.className = "bodyIconLeft";
  img.style.width = "27px";
  img.style.height = "22px";
  img.OpenedSrc = "/nst/images/folder.open.gif";
  img.ClosedSrc = "/nst/images/folder.gif";
  if (node.style.display == 'none') {
    img.src = img.ClosedSrc;
  } else {
    img.src = img.OpenedSrc;
  }
  img.toggleId = nodeId;

  img.onclick = function() {
    var icon = this;
    var entity = document.getElementById(icon.toggleId);
    if (entity.style.display == 'none') {
	entity.style.display = 'inline';
	icon.src = img.OpenedSrc;
    } else {
	entity.style.display = 'none';
	icon.src = img.ClosedSrc;
    }
  }

  img.onmouseover = function(event) {
    var tt = NstDom.ttAction("Click") + " on icon to "
      + NstDom.ttEmphasis("hide") + "/" + NstDom.ttEmphasis("show")
      + " contents";
    domTT_activate(this, event, 'content', tt, 'width', 240);
  }

  return img;
}

/* Create a <img> element that user can click on to jump to the top of
 * the display. */

NstDom.createTopOfPageIcon = function() {

    // Build image icon and append to <div>
    var img = document.createElement("img");
    img.className = "bodyIconRight";
    img.style.width = "15px";
    img.style.height = "23px";
    img.src = "/nst/images/links_top_arrow.gif";
    img.onclick = function() {
	window.location = "#top";
    }

    img.onmouseover = function(event) {
      var tt = NstDom.ttAction("Go") + " To The " 
        + NstDom.ttNav("Top") + " Of The Page";

      domTT_activate(this, event, 'content', tt, 'width', 200);
    }

    return img;
}


/* Create a <img> element that user can click on to jump to the bottom of
 * the display. */

NstDom.createBottomOfPageIcon = function() {

    // Build image icon and append to <div>
    var img = document.createElement("img");
    img.className = "bodyIconRight";
    img.style.width = "15px";
    img.style.height = "23px";
    img.src = "/nst/images/links_bottom_arrow.gif";
    img.onclick = function() {
	window.location = "#bottom";
    }

    img.onmouseover = function(event) {
      var tt = NstDom.ttAction("Go") + " To The " + NstDom.ttNav("Bottom")
        + " Of The Page";

      domTT_activate(this, event, 'content', tt, 'width', 220);
    }

    return img;
}

/* Create a <img> element that user can click on to jump to the previous section
 * the display. */

NstDom.createPrevSectIcon = function() {

    // Build image icon and append to <div>
    var img = document.createElement("img");
    img.className = "bodyIconRight";
    img.style.width = "15px";
    img.style.height = "20px";
    img.src = "/nst/images/links_up_arrow.gif";
    img.onclick = function() {
	NstDom.moveToPreviousSection(this);
    }

    img.onmouseover = function(event) {
      var tt = NstDom.ttAction("Go") + " To The " + NstDom.ttNav("Previous")
        + " Section";
      domTT_activate(this, event, 'content', tt, 'width', 200);
    }

    return img;
}

/* Create a <img> element that user can click on to jump to the next section
 * the display. */

NstDom.createNextSectIcon = function() {

    // Build image icon and append to <div>
    var img = document.createElement("img");
    img.className = "bodyIconRight";
    img.style.width = "15px";
    img.style.height = "20px";
    img.src = "/nst/images/links_down_arrow.gif";
    img.onclick = function() {
	NstDom.moveToNextSection(this);
    }

    img.onmouseover = function(event) {
      var tt = NstDom.ttAction("Go") + " To The " + NstDom.ttNav("Next")
        + " Section";
      domTT_activate(this, event, 'content', tt, 'width', 180);
    }

    return img;
}

/* Create a <img> element that user can click on to jump to the previous section
 * the display. */

NstDom.createHomeIcon = function() {

    // Build image icon and append to <div>
    var img = document.createElement("img");
    img.className = "bodyIconRight";
    img.style.width = "21px";
    img.style.height = "22px";
    img.src = "/nst/images/links_home.gif";

    img.onclick = function() {
      if (NstDom.isProbeBuild()) {
        window.location = "/nstwui/index.cgi";
      } else {
        window.location = "/nst/welcome.html";
      }
    }

    img.onmouseover = function(event) {
      var pname = "NST Welcome";
      if (NstDom.isProbeBuild()) {
         pname = "NST WUI Index";
      }
      var tt = NstDom.ttAction("Go",false) + " To The "
        + NstDom.ttNav(pname,true) + " Page";
      domTT_activate(this, event, 'content', tt, 'width', 220);
    }

    return img;
}

/* Create a <img> element that user can click on to jump to the previous section
 * the display. */

NstDom.createReloadIcon = function() {

    var img = document.createElement("img");
    img.className = "bodyIconRight";
    img.style.width = "23px";
    img.style.height = "22px";
    img.src = "/nst/images/links_reload.gif";
    img.onclick = function() {
	NstDom.reloadSection(this);
    }

    img.onmouseover = function(event) {
      var tt = NstDom.ttAction("Reload") + " The "
        + NstDom.ttNav("Current") + " Page At This " 
        + NstDom.ttValue("Position","false");
      domTT_activate(this, event, 'content', tt, 'width', 280);
    }

    return img;
}

/* NstDom.createExitIcon()
 *
 * Create a <img> element that user can click on to jump to exit the current
 * page. */

NstDom.createExitIcon = function() {
  var img = document.createElement("img");
  img.className = "bodyIconRight";
  img.style.width = "20px";
  img.style.height = "15px";
  img.style.paddingBottom = "3px";
  img.src = "/nst/images/links_left_arrow.gif";
  img.onclick = function() {
    if (NstDom._ExitFunction) {
      NstDom._ExitFunction(this);
    } else if (NstDom._ExitUrl) {
      if (NstDom._ExitUrl == "None") {
	window.close();
      } else {
	window.location = NstDom._ExitUrl;
      }
    } else {
      window.location = "/nst/index.cgi";
    }
  }

  img.onmouseover = function(event) {
    domTT_activate(this, event, 'content', NstDom._ExitToolTip,
		   'width', NstDom._ExitToolTipWidth);
  }

  return img;
}

/* NstDom.headerEnhanceBody(node)
 *
 *   Enhances the 'node' give as if it is a header node within the main body.
 *
 * node - The DOM node to enhance (must not be null and have the id 
 * attribute set). */

NstDom.headerEnhanceBody = function(node) {

  if (node && node.id && !node.HasBeenEnhanced) {
    // Mark node as "header" enhanced (prevent duplicate enhancements)
    node.HeaderEnhanced = true;

    var combined = document.createElement("div");
    combined.className = "fullWidth";

    var header = document.createElement("div");
    header.className = 'bodyHeaderLeft';

    // If there is an associated area with the header, add open/close icon
    var hideCloseNode = document.getElementById(node.id + "-area");
    if (hideCloseNode) {
	var img = NstDom.createOpenCloseIcon(node.id + "-area");
    //
    // Save access to icon node...
        node.icon = img;
	img.className = "bodyOpenCloseIconLeft";
	header.appendChild(img);
    } else {
      // Add a bit of indent if no leading image
      var indent = document.createElement("span");
      indent.style.width = ".2em";
      indent.appendChild(document.createTextNode("\u00a0"));
      header.appendChild(indent);
    }

    // Move original content into new div
    //    var origContent = document.createElement("div");
    //    origContent.className = 'bodyHeaderLeft';
    while (node.firstChild) {
	var removed = node.removeChild(node.firstChild);
	header.appendChild(removed);
    }
    //    header.appendChild(origContent);

    //    header.firstChild.style.paddingLeft = ".2em";

    combined.appendChild(header);

    var icons = document.createElement("div");
    icons.className = 'floatRight';

    if (NstDom._ExitUrl || NstDom._ExitFunction) {
      icons.appendChild(NstDom.createExitIcon());
    }
    icons.appendChild(NstDom.createHomeIcon());
    icons.appendChild(NstDom.createReloadIcon());
    icons.appendChild(NstDom.createTopOfPageIcon());
    icons.appendChild(NstDom.createBottomOfPageIcon());
    icons.appendChild(NstDom.createPrevSectIcon());
    var img = NstDom.createNextSectIcon()
    img.style.paddingRight = ".2em";
    icons.appendChild(img);
    combined.appendChild(icons);

    var clear = document.createElement("br");
    clear.className = "clear";
    combined.appendChild(clear);

    node.appendChild(combined);
  }
}

/** NstDome.showNstConsoleTooltip(event)
 *
 * Show DOM tooltip for JavaScript console. */

  NstDom.showNstConsoleTooltip = function(node, event) {
  var tt = "<div class=\"line1px\">"
        + NstDom.ttAction("Toggle") + " the "
        + NstDom.ttValue("Display",false) + " of the "
        + NstDom.ttEmphasis("NST JavaScript Console",true)
        + " at the " + NstDom.ttEmphasis("Bottom",false)
        + " of the page.</div>"
        + "<br /><div class=\"line1px\" style=\"margin-bottom: 0.3em;\">"
        + NstDom.ttAction("About") + ":&nbsp;&nbsp;"
        + NstDom.ttEmphasis("NST")
        + " <u>" + NstDom.ttEmphasis("J") + "</u>"
        + NstDom.ttEmphasis("avaScript") + " <u>" + NstDom.ttEmphasis("C") + "</u>"
        + NstDom.ttEmphasis("onsole") + "&nbsp;&nbsp;v"
        + NstDom.ttValue(NstConsole.getVersion())
        + "</div>"
        + "An " + NstDom.ttAction("Interactive") + " "
        + NstDom.ttValue("Interface",false)
        + " and " + NstDom.ttValue("Library",false)
        + " of " + NstDom.ttEmphasis("JavaScript") + " "
        + NstDom.ttValue("Objects",false) + ", "
        + NstDom.ttValue("Functions",false) + ", "
        + NstDom.ttValue("Methods",false) + " and "
        + NstDom.ttValue("Properties",false)
        + " designed to " + NstDom.ttAction("Help") + " "
        + NstDom.ttEmphasis("Software Engineers") + " "
        + NstDom.ttAction("Diagnose") + " and " + NstDom.ttAction("Develop")
        + " " + NstDom.ttEmphasis("JavaScript Code",true)
        + " for " + NstDom.ttAction("Building") + " "
        + NstDom.ttValue("Dynamic Web",true) + " pages.";

      domTT_activate(node, event, 'content', tt, 'width', 520);
}

/* Create a <img> element that user can click on to toggle display
 * of console. */

NstDom.createConsoleIconBanner = function() {

    var img = document.createElement("img");
    img.className = "footerIcon";
    img.style.width = "19px";
    img.style.height = "22px";
    img.src = "/nst/images/toggle_console.gif";
    img.onclick = function() {
	// Deactivate tooltip prior to console close (otherwise we get a 
	// stuck tooltip on the page)
	domTT_deactivate(this.id);

	NstConsole.toggleConsole();
    }

    img.onmouseover = function(event) {
      NstDom.showNstConsoleTooltip(this, event);
    }

    return img;
}

/* Show the tooltip for the "enable side navigation" action.
 *
 * pname - Optiona name of the page to show. */

NstDom.showEnableFramesTooltip = function(node, event, pname) {
  if (!pname) {
    if (NstDom.isProbeBuild()) {
      pname = "NST WUI Index";
    } else {
      pname = "NST Welcome";
    }
  }

  var tt = NstDom.ttNote("Go",false) + " To The " 
    + NstDom.ttEmphasis(pname,true) + " Page - "
    + NstDom.ttValue("Enable Side Navigation",true)
    + ".<br /><br />"
    + NstDom.ttNote("Hover",false) + " to "
    + NstDom.ttEmphasis("Enable",true) + " the "
    + NstDom.ttValue("NST WUI Menu Bar") + ".";

  domTT_activate(node, event, 'content', tt, 'width', '390',
                 'offsetX', 40, 'offsetY', 50);
}

/* NstDom.createStartPageIconBanner()
 *
 * Create a <img> element that user can click on to jump to the
 * start page (bit NST logo. */

NstDom.createStartPageIconBanner = function() {

  // Create default, then tweak for banner area
  var img = document.createElement("img");
  img.id = 'nstBannerStartIcon';
  img.style.borderStyle = "none";
  img.style.padding = "0px";
  img.style.margin = "0px";

  var url = "/nst/welcome.html";
  var pname = "NST Welcome";
  if (NstDom.isProbeBuild()) {
    url = "/nstwui/main.cgi";
    if (sysInfo && sysInfo['NST_REG_CODE']) {
      img.src = "/nstwui/images/splasnstpro157x46.gif";
      pname = "NST Pro Start";
    } else {
      img.src = "/nstwui/images/splasnst157x46.gif";
      pname = "NST Start";
    }
  } else {
    img.src = "/nst/images/splasnst157x46.gif";
  }

  //
  // Firefox  viewing '.xml' files needs tweaking...
  if (isFirefox()) {
    if (window.location.pathname.search(/\.xml$/) != -1) {
      img.style.marginBottom = "-6px";
    }
  }
  img.style.height = "46px";
  img.style.width = "157px";

  img.onmouseover = function(event) {
    var tt = NstDom.ttAction("Go") + " To The "
      + '<span id="_startPageNameTT" class="ttNav">'
      + pname + "</span> Page";
    domTT_activate(this, event, 'content', tt, 'width', 220);
  }

  // Make a link so user can right click
  var a = document.createElement("a");
  a.appendChild(img);
  a.href = url;
  a.style.height = img.style.height;
  a.style.width = img.style.width;
  
  return a;
}

/* NstDom.createSourceForgeIconBanner()
 *
 * Create a <img> element that user can click on to jump to the
 * NST Source Forge page. */

NstDom.createSourceForgeIconBanner = function() {

  // Create default, then tweak for banner area
  var img = document.createElement("img");
  img.src = "/nst/images/sf-icon2.gif";
  img.style.borderStyle = "none";
  img.style.margin = "0px";
  img.style.padding = "0px";
  img.style.height = "37px";
  img.style.width = "125px";

  img.onmouseover = function(event) {
    var tt = NstDom.ttAction("Go") + " To The "
      + NstDom.ttNav("NST Project",true) + " Page";
    domTT_activate(this, event, 'content', tt, 'width', 220);
  }

  // Make a link so user can right click
  var a = document.createElement("a");
  a.appendChild(img);
  a.href = "http://sourceforge.net/projects/nst";
  a.style.height = img.style.height;
  a.style.width = img.style.width;
  
  return a;
}

/* NstDom.ttSortable(node, event, sortedBy)
 *
 *   Displays a tooltop for a table column header which can be clicked
 *   on to sort the contents of the table.
 *
 * node - The DOM node to the column header (a <th> entity).
 * event - The event which trigger the tooltip.
 * sortedBy - A label of what we will sort on ("IP Address"). */

NstDom.ttSortable = function(node, event, sortedBy) {
  var tt = NstDom.ttNote("Sort",false) + " the "
    + NstDom.ttEmphasis("Table Rows",false) + " by: "
    + NstDom.ttValue(sortedBy, true);

  domTT_activate(node, event, 'content', tt, 'width',
                 calcDomTTLen(sortedBy.length, 100, 240, 1600));

}

/* Show the tooltip for the "disable side navigation" action. */

NstDom.showDisableFramesTooltip = function(node, event) {

  var tt = NstDom.ttNote("Reload", false) + " Contents Of The " 
    + NstDom.ttEmphasis("Current",true) + " Page - "
    + NstDom.ttValue("Disable Side Navigation",true)
    + ".<br /><br />"
    + NstDom.ttNote("Hover",false) + " for more than "
    + NstDom.ttValue("4 secs", true) + " to "
    + NstDom.ttEmphasis("Disable",true) + " the "
    + NstDom.ttValue("NST WUI Menu Bar") + ".";

  domTT_activate(node, event, 'content', tt, 'width', '430',
                 'offsetX', 40, 'offsetY', 10);
}

/* Create a <img> element that user can click on to disable side navigation. */

var menuBarDisableTimeoutID;

NstDom.createDisableFramesIconBanner = function() {
  var a = document.createElement("a");
  a.target = "_top";
  a.href = window.location;

  a.className = "navLinkA";
  a.onmouseover = function(event) {
    NstDom.showDisableFramesTooltip(this, event);
    menuBarDisableTimeoutID = setTimeout("NstDom.disableFramesIconBanner()", 4000);
  }

  a.onmouseout = function(event) {
    domTT_mouseout(this, event);
    clearTimeout(menuBarDisableTimeoutID);
  }

  var img = document.createElement("img");
  img.className = "footerIcon";
  img.style.width = "24px";
  img.style.height = "24px";
  img.src = "/nst/images/noframes.gif";
  //
  // Firefox  viewing '.xml' files needs tweaking...
  if (isFirefox()) {
    if (window.location.pathname.search(/\.xml$/) != -1) {
      img.style.marginBottom = "-6px";
    }
  }

  a.appendChild(img);
  return a;
}

/* Create a <img> element that user can click on to jump to the top of
 * the display (for display in banner). */

NstDom.createTopOfPageIconBanner = function() {

  // Create default, then tweak for banner area
  var img = NstDom.createTopOfPageIcon();
  img.src = "/nst/images/links_footertop_arrow.gif";
  img.style.width = "17px";
  img.style.height = "23px";
  img.className = "footerIcon";

  return img;
}

/* Create a <img> element that user can click on to jump to the previous section
 * the display (suitable for display in a banner region). */

NstDom.createPrevSectIconBanner = function() {

  // Create default, then tweak for banner area
  var img = NstDom.createPrevSectIcon();
  img.src = "/nst/images/links_footerprev_arrow.gif";
  img.style.width = "17px";
  img.style.height = "23px";
  img.className = "footerIcon";

  return img;

}

/* Create a <img> element that user can click on to view the source
 * code (on a banner area).
 *
 * e - The HTML entity to modify (typically a <a> or <img> object).
 *
 * fname - Full path to the file to view the source of (or full URL on
 * Internet).
 *
 * ttname - Option name of file to show in tooltip (if different than
 * fname).  */

NstDom.setSourceViewHandler = function(e, fname, ttname) {

    if (!ttname) {
      ttname = fname;
    }

    // Determine if we should use PHP viewer or generice tail viewer
    if (fname.substring(0,7) == "http://") {
      // If URL, then just go to the URL
      e.onclick = function() {
        window.open(fname);
      }
    } else if (fname.indexOf(".php") > 0) {
      e.onclick = function() {
 	window.open("/nstwui/php/system/ShowSource.php?return=None&filename="
                    + escape(fname));
      }
    } else {
      e.onclick = function() {
 	window.open("/nstwui/cgi-bin/system/tail.cgi?lines=all&toggletextmode=ASCII&filename="
                    + escape(fname));
      }
    }
    e.onmouseover = function(event) {
      var tt = "<div class=\"line1px\">" + NstDom.ttNote("View") + " The " 
        + NstDom.ttEmphasis("Source Code",true) + "</div>"
        + "<table>"
        + "<tr><th align=\"left\">Source File"
        + NstDom.ttNormal(":", false) + "</th>"
        + "<td align=\"left\">" + NstDom.ttValue(ttname, true) 
        + "</td></tr></table>";
      domTT_activate(this, event, 'content', tt, 'width',
                     calcDomTTLen(ttname.length, 100, 300, 1600));
    }
}

/* Create a <img> element that user can click on to view the source
 * code (on a banner area).
 *
 * fname - Full path to the file to view the source of (or full URL on
 * Internet).
 *
 * ttname - Option name of file to show in tooltip (if different than
 * fname).  */

NstDom.createSourceViewIconBanner = function(fname, ttname) {

    if (!ttname) {
      ttname = fname;
    }

    // Build image icon and append to <div>
    var img = document.createElement("img");
    img.className = "footerIcon";
    img.style.width = "19px";
    img.style.height = "22px";
    img.src = "/nst/images/links_viewpage.gif";

    NstDom.setSourceViewHandler(img, fname, ttname);
    return img;
}

/* Create a <img> element that user can click on to jump to the previous section
 * the display. */

NstDom.createHomeIconBanner = function() {

  // Create default, then tweak for banner area
  var img = NstDom.createHomeIcon();
  img.src = "/nst/images/links_footerhome.gif";
  img.style.width = "20px";
  img.style.height = "23px";
  img.className = "footerIcon";
  //
  // Firefox  viewing '.xml' files needs tweaking...
  if (isFirefox()) {
    if (window.location.pathname.search(/\.xml$/) != -1) {
      img.style.marginBottom = "-6px";
    }
  }

  return img;
}

/* Create a <img> element that user can click on to jump to the previous section
 * the display. */

NstDom.createReloadIconBanner = function() {

  // Create default, then tweak for banner area
  var img = NstDom.createReloadIcon();
  img.src = "/nst/images/header_reload.gif";
  img.style.width = "23px";
  img.style.height = "22px";
  img.className = "footerIcon";
  //
  // Firefox  viewing '.xml' files needs tweaking...
  if (isFirefox()) {
    if (window.location.pathname.search(/\.xml$/) != -1) {
      img.style.marginBottom = "-6px";
    }
  }

  return img;

}

/* NstDom.createHomeIconNote()
 *
 * Create a <img> element that user can click on to jump to the "home" page
 * (to be used in Note areas). */

NstDom.createHomeIconNote = function() {

  // Create default, then tweak for banner area
  var img = NstDom.createHomeIcon();
  img.src = "/nst/images/links_home_note.gif";
  img.style.width = "21px";
  img.style.height = "22px";
  img.className = "noteIconRight";

  return img;

}

/* NstDom.createExitIconNote()
 *
 * Create a <img> element that user can click on to jump to exit the current
 * page (suitable for display in note area). */

NstDom.createExitIconNote = function() {

  var img = NstDom.createExitIcon();
  img.className = "noteIconRight";

  return img;

}

/* Create a <img> element that user can click on to toggle the open/close
 * state (hide/show) another element on the page (for note area). 
 *
 * nodeId - ID of other element to toggle the state of. NOTE: If the nodeId
 * does not exist, then this method will return null. */

NstDom.createOpenCloseIconNote = function(nodeId) {

  // Create default, then tweak for banner area
  var img = NstDom.createOpenCloseIcon(nodeId);

  return img;

}

/* NstDom.createReloadIconNote()
 *
 * Create a <img> element that user can click on to jump to refresh the
 * current page (to be used in Note areas). */

NstDom.createReloadIconNote = function() {

  // Create default, then tweak for banner area
  var img = NstDom.createReloadIcon();
  img.src = "/nst/images/links_reload_note.gif";
  img.style.width = "23px";
  img.style.height = "22px";
  img.className = "noteIconRight";

  return img;

}

/* NstDom.createTopOfPageIconNote()
 *
 * Create a <img> element that user can click on to jump to the top of page
 * (to be used in Note areas). */

NstDom.createTopOfPageIconNote = function() {

  // Create default, then tweak for banner area
  var img = NstDom.createTopOfPageIcon();
  img.src = "/nst/images/links_top_arrow_note.gif";
  img.style.width = "15px";
  img.style.height = "23px";
  img.className = "noteIconRight";

  return img;

}

/* NstDom.createBottomOfPageIconNote()
 *
 * Create a <img> element that user can click on to jump to the bottom of page
 * (to be used in Note areas). */

NstDom.createBottomOfPageIconNote = function() {

  // Create default, then tweak for banner area
  var img = NstDom.createBottomOfPageIcon();
  img.src = "/nst/images/links_bottom_arrow_note.gif";
  img.style.width = "15px";
  img.style.height = "23px";
  img.className = "noteIconRight";

  return img;

}

/* NstDom.createPrevSectIconNote()
 *
 * Create a <img> element that user can click on to jump to the previous section
 * (to be used in Note areas). */

NstDom.createPrevSectIconNote = function() {

  // Create default, then tweak for banner area
  var img = NstDom.createPrevSectIcon();
  img.src = "/nst/images/links_up_arrow_note.gif";
  img.style.width = "15px";
  img.style.height = "20px";
  img.className = "noteIconRight";

  return img;

}

/* NstDom.createNextSectIconNote()
 *
 * Create a <img> element that user can click on to jump to the next section
 * (to be used in Note areas). */

NstDom.createNextSectIconNote = function() {

  // Create default, then tweak for banner area
  var img = NstDom.createNextSectIcon();
  img.src = "/nst/images/links_down_arrow_note.gif";
  img.style.width = "15px";
  img.style.height = "20px";
  img.className = "noteIconRight";

  return img;

}

/* NstDom.headerEnhanceNote(node)
 *
 *   Enhances the 'node' give as if it is a header node within a Notes section.
 *
 * node - The DOM node to enhance (must not be null and have the id 
 * attribute set). */

NstDom.headerEnhanceNote = function(node) {

  if (node && node.id && !node.HasBeenEnhanced) {
    // Mark node as "header" enhanced (prevent duplicate enhancements)
    node.HeaderEnhanced = true;

    var combined = document.createElement("div");
    combined.className = "fullWidth";

    var header = document.createElement("div");
    header.className = 'noteHeaderLeft';

    // If there is an associated area with the header, add open/close icon
    var hideCloseNode = document.getElementById(node.id + "-area");
    if (hideCloseNode) {
	var img = NstDom.createOpenCloseIconNote(node.id + "-area");
	img.className = "noteOpenCloseIconLeft";
	header.appendChild(img);
    }

    while (node.firstChild) {
	var removed = node.removeChild(node.firstChild);
	header.appendChild(removed);
    }

    combined.appendChild(header);

    var icons = document.createElement("div");
    icons.className = 'floatRight';

    if (NstDom._ExitUrl || NstDom._ExitFunction) {
      icons.appendChild(NstDom.createExitIconNote());
    }
    icons.appendChild(NstDom.createHomeIconNote());
    icons.appendChild(NstDom.createReloadIconNote());
    icons.appendChild(NstDom.createTopOfPageIconNote());
    icons.appendChild(NstDom.createBottomOfPageIconNote());
    icons.appendChild(NstDom.createPrevSectIconNote());
    icons.appendChild(NstDom.createNextSectIconNote());
    combined.appendChild(icons);

    var clear = document.createElement("br");
    clear.className = "clear";
    combined.appendChild(clear);

    node.appendChild(combined);
  }
}

/* NstDom.headerEnhance()
 *
 *   Scans for NstDom.registeredSection(s) which have a "enhanceable" class
 *   type and enhances them. */

NstDom.headerEnhance = function() {

  for (var sidx in NstDom._OrderedHeaders) {
    var id = NstDom._OrderedHeaders[sidx];
    var node = document.getElementById(id);
    if (node) {
      if (node.className == "bodyHeader") {
        NstDom.headerEnhanceBody(node);
      } else if (node.className == "noteHeader") {
        NstDom.headerEnhanceNote(node);
      }
    }
  }
}

/* NstDom.addIdEnhance()
 *
 *   Registers function to be applied to a specific DOM node (by it's
 *   "id" attribute) AFTER the document is loaded.
 *
 * Example:
 * 
 *
 * id - The "id" of the entity to be enhanced.
 *
 * f - The JavaScript function to be invoked (it will be passed the DOM
 * node corresponding to the "id"). */

NstDom.addIdEnhance = function(id, f) {
  if (id) {
    NstDom._IdEnhancements[id] = f;
  }
}

/* NstDom.idEnhance()
 *
 *   Applies enhancement method to each node registered by the
 *   NstDom.addIdEnhance() method. */

NstDom.idEnhance = function() {

  for (var id in NstDom._IdEnhancements) {
    var node = document.getElementById(id);
    var f = NstDom._IdEnhancements[id];
    // If node found and function exists, then apply the function
    if (node && f) {
      f(node);
    }
  }

  // Clear the array of applied enhancements
  NstDom._IdEnhancements = [];

}

/* NstDom.showKernelSourceToolTip(node, event[, srcName])
 *
 *   Shows tool tip for node (assuming it's a link to a kernel source file).
 *
 * node - DOM node to show tool tip for.
 * event - Window event.
 * srcName - Optional source name (if omitted, we'll use text of child). */

NstDom.showKernelSourceToolTip = function(node, event, srcName) {
  if (!srcName) {
    srcName = NstDom.getTextContent(node);
  }

  // If given, or able to determine source name
  if (srcName) {
    // build tool tip contents
    var tt = "View the contents of the kernel source file: "
	+ NstDom.ttValue(srcName, true)
	+ " at " 
	+ NstDom.ttSite(node.href)
	+ ".";

    // and show it
    domTT_activate(node, event, 'content', tt, 'width', 460);
  }

}

/* NstDom.replaceKernelSource(node)
 *
 *   If DOM 'node' contains a single #TEXT entity, we will replace the
 *   node with a <a> element that should take the user to the kernel
 *   source location. */

NstDom.replaceKernelSource = function(node) {
    var srcName = NstDom.getTextContent(node);
    // If able to extract source name
    if (srcName) {
	// Build a <a> node
	var a = document.createElement("a");
	a.href = "http://lxr.linux.no/source/" + srcName;
	a.className = "kernelSource";
	a.target = "_blank";
	a.appendChild(node.firstChild); // document.createTextNode(srcName);
	a.onmouseover = function(e) {
	    NstDom.showKernelSourceToolTip(this, e);
	}

	// Replace old node with new <a> node
	node.parentNode.replaceChild(a, node);
    }
}

/* NstDom.spanEnhance()
 * 
 *   Run's one time after page load. Looks at the "class" attribute on
 *   all "<span>" entities and does the following:
 *
 *   "kernelSource" - If <span> contained a single child, entire span
 *   will be replaced with a <a> link to jump to the source code. */

NstDom.spanEnhance = function() {
  var spans = document.getElementsByTagName("span");
  for (var i = 0; i < spans.length; i++) {
    var span = spans[i];
    var cname = span.className;
    if (cname == "kernelSource") {
      NstDom.replaceKernelSource(span);
    } else if (cname == "sourceFile") {
      if (span.title) {
        NstDom.setSourceViewHandler(span, span.title, NstDom.getTextContent(span));
        span.title = null;
      }
    } else if (NstDom[cname + "SpanEnhance"]) {
      // Handle case where NstDom was updated with a new "ClassSpanEnhance" 
      // method (list NstWui might do)
      NstDom[cname + "SpanEnhance"](span);
    }
  }
}

/* NstDom.setCssClassAttr(classSelector, attr, val, omit)
 *
 * Function to set a CSS Class Attributes with javascript.
 * This function will step through each DOM 2 style sheet defined
 * on the page looking for the matching: 'classSelector' class
 * or style rule for the given 'attr' attribute and associated
 * 'val' value. If a match is found, the 'attr' attribute
 * will be set to 'val' value. If a match is not found and
 * the 'omit' option is set to false, the 'attr' attribute
 * and 'val' value will be added to the last style sheet
 * at the end of it.
 *
 * classSelector - Selector text for the class rule. This value
 *                 is case insensitive.
 *                 (e.g., 'div.niceTitle')
 * attr          - A class rule style sheet attribute.
 *                 (e.g., 'fontSize')
 * val           - The value to set the class rule style
 *                 sheet attribute to.
 *               - (e.g., '24px')
 * omit          - Set to true to omit adding the attribute and
 *                 value if a match was not found.
 *               - Set to false to add the attribute and value
 *                 if a match was not found.
 *
 * Returns: true  - a match was found and attribute set.
 *          false - no match was found and attribute was not set.
 *
 * Example 1: NstDom.setCssClassAttr('div.niceTitle', 'fontSize', '33px', true)
 *
 * Note 1:    If the Selector text for the class rule contains more than
 *            one name, separate each name with a comma and space:
 *
 * Example 2: NstDom.setCssClassAttr('h1.noteHeader, h1.bodyHeader', 'marginTop', '2.1em', true)
 *
 * Note 2:    Both Mozilla and Chrome group the entire Selector text for
 *            the class rule as one string where as IE breaks up the
 *            Selector text as individual class rules. This function handles
 *            this browser difference.
*/

NstDom.setCssClassAttr = function(classSelector, attr, val, omit) {
  //
  // set 'classSelector' case insensitive...
  var lc_cs = classSelector.toLowerCase();
  //
  // collection containing all the rules of the style sheet...
  var cssRules;

  //
  // If 'omit' was not specified do not add...
  if (omit == null) {
    omit = true;
  }

  // ********************
  // ***SET CLASS RULE***
  // ********************

  //
  // if IE...
  if (document.all) {
  //
  // style sheet collection name for IE...
    cssRules = 'rules';
  //
  // 
    var found_rule = false;
  //
  // Create a class selector text array in case of multiple
  // 'classSelector' names for IE...
    var ie_cs = lc_cs.split(/, /);
  //
  // Step thru page style sheets and class rules looking for
  // matching 'attr' attribute to set...
    for (var s = 0; s < document.styleSheets.length; s++) {
      for (var r = 0; r < document.styleSheets[s][cssRules].length; r++) {
        if (document.styleSheets[s][cssRules][r].selectorText) {
  //
  // check to set each individual class rule... 
          for (var c = 0; c < ie_cs.length; c++) {
            if (document.styleSheets[s][cssRules][r].selectorText.toLowerCase() == ie_cs[c]) {
              if (document.styleSheets[s][cssRules][r].style[attr]) {
                document.styleSheets[s][cssRules][r].style[attr] = val;
                found_rule = true;
              }
            }
          }
        }
      }
      if (found_rule) {
        return true;
      }
    }
  } else if (document.getElementById) {
  //
  // Other browsers (At least: Mozilla and Chrome)...
  //
  // style sheet collection name for Mozilla and Chrome...
    cssRules = 'cssRules';
  //
  // Step thru page style sheets and class rules looking for
  // matching 'attr' attribute to set...
    for (var s = 0; s < document.styleSheets.length; s++) {
      for (var r = 0; r < document.styleSheets[s][cssRules].length; r++) {
        if (document.styleSheets[s][cssRules][r].selectorText) {
          if (document.styleSheets[s][cssRules][r].selectorText.toLowerCase() == lc_cs) {
            if (document.styleSheets[s][cssRules][r].style[attr]) {
              document.styleSheets[s][cssRules][r].style[attr] = val;
              return true;
            }
          }
        }
      }
    }
  }

  // ********************
  // ***ADD CLASS RULE***
  // ********************

  //
  // If we get here then no class rule with attribute match was found...
  if (!omit) {
  //
  // Try to add class rule to the last style sheet at the end...
  var s = document.styleSheets.length - 1;
    if (document.styleSheets[s].insertRule) {
  //
  // Mozilla and Chrome add class rule method...
      try {
        document.styleSheets[s].insertRule(lc_cs + ' { ' + attr + ': ' + val + '; }', document.styleSheets[s][cssRules].length);
        return true;
      } catch (e) {
        return false;
      }
    } else if (document.styleSheets[s].addRule) {
  //
  // IE add class rule method...
      try {
  //
  // check to add each individual class rule at end of last style sheet...
        for (var c = 0; c < ie_cs.length; c++) {
          document.styleSheets[s].addRule(ie_cs[c], attr + ': ' + val + ';', -1);
        }
        return true;
      } catch (e) {
        return false;
      }
    }
  }
  return false;
}

/* NstDom.setInputSelection(n, start, nchars)
 *
 * Selects text within an <input> box (might work on <textarea> a
 * well).
 *
 * node - The DOM node to update (or ASCII ID of node).
 *
 * start - The starting position of the first character to hightlight
 * within the <input> box (0 corresponds to first character).
 *
 * nchars - Number of characters to highlight (must be greater than 0).
 */

NstDom.setInputSelection = function(node, start, nchars) {
  node = NstDom.getNode(node);
  if (node && (nchars > 0)) {
    // Compute end of selection
    var end = start + nchars;

    if (node.setSelectionRange) {
      // Firefox
      node.focus();
      node.setSelectionRange(start, end);
    } else if (node.createTextRange) {
      // IE
      var range = node.createTextRange();
      range.collapse(true);
      range.moveEnd('character', end);
      range.moveStart('character', start);
      range.select();
    }
  }
}


/**
 * Get the date in the form of: "yyyy-mm-dd".
 *
 * d - JavaScript Date object to get information from.
 *
 * return String in the form of "yyyy-mm-dd". */

NstDom.formatIsoDate = function(d) {
  var mstr = "" + (1 + d.getMonth());
  if (mstr.length == 1) {
    mstr = "0" + mstr;
  }

  var dstr = "" + d.getDate();
  if (dstr.length == 1) {
    dstr = "0" + dstr;
  }

  return d.getFullYear() + "-" + mstr + "-" + dstr;
}


/**
 * Get the date in the form of: "HH:MM:SS".
 *
 * d - JavaScript Date object to get information from.
 *
 * return String in the form of "HH:MM:SS". */

NstDom.formatTime = function(d) {
  var hstr = NstDom.padLeading(d.getHours(), 2);
  var mstr = NstDom.padLeading(d.getMinutes(), 2);
  var sstr = NstDom.padLeading(d.getSeconds(), 2);

  return (hstr + ":" + mstr + ":" + sstr);
}


/**
 * Get the date in the form of: "HH:MM:SS.mmm" (to millisecond).
 *
 * d - JavaScript Date object to get information from.
 *
 * return String in the form of "HH:MM:SS.mmm". */

NstDom.formatTimeMillis = function(d) {
  return NstDom.formatTime(d) + "." + NstDom.padLeading(d.getMilliseconds(), 3);
}

/*
 * Takes a associative array and formats to string for appending to URL.
 *
 * aa - Associative array of key/value pairs.
 *
 * returns String in form of "key0=val0&key1=val1..." where
 * key/value pairs come from the 'aa' associative array and are properly
 * escaped.
 */

NstDom.formatUrlParams = function(aa) {
  var prefix = "";
  var s = "";
  for (var key in aa) {
    s += prefix + escape(key) + "=" + escape(aa[key]);
    prefix = "&";
  }
  return s;
}

/*
 * Takes a URL and appends a associative array as parameters to the
 * end properly escaped for submission.
 *
 * url - Base URL to append escaped parameters to.
 * params - Associative array of parameters to append to end.
 *
 * returns String in form of "url?key0=val0&key1=val1..." where
 * key/value pairs come from the params associative array.
 */

NstDom.formatUrlWithParams = function(url, params) {
  var s = NstDom.formatUrlParams(params);
  if (s.length > 0) {
    url = url + "?" + s;
  }
  return url;
}

/*
 * Builds a "return" URL such that the current "return" values are
 * embedded within URL so they can be restored when we come back.
 *
 * url - URL string without any parameters (or null if we should come
 * back to current page).
 *
 * params - Array of custom parameters to append to url (pass [] if none).
 *
 * return URL which can be passed as a "return" parameter when calling
 * a supporting page. */

NstDom.buildReturnUrl = function(url, params) {
  var p = new Properties();

  // If passed null as URL, add base URL for current page
  if (url == null) {
    url = window.location.protocol + "//" + window.location.host;
    if (window.location.port) {
      url += ":" + window.location.port;
    }
    url += window.location.pathname;
  }

  if (params == null) {
    params = [];
  }

  p.fromUrlParams(window.location.search);
  var keys = [ "return", "return_from", "return_label" ];
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    var val = p.getValue(key, null);
    if (val != null) {
      params[key] = val;
    }
  }
  return NstDom.formatUrlWithParams(url, params);
}

/*
 * Looks for "return", "return_label" and "return_from" in the current
 * window location (unless you supply a url) and for each key found in
 * url adds a corresponding "_back" entry to the params array.
 *
 * url - URL string to look for "return", "return_label", ... in (or
 * pass null if we should use URL from current page).
 *
 * params - Associative array to set the "_back" key/value pairs in
 * (pass [] to start with empty array).
 *
 * return params array with the "XXX_back" values set. */

NstDom.addReturnBack = function(url, params) {
  var p = new Properties();

  p.fromUrlParams((url != null) ? url : window.location.search);
  var keys = [ "return", "return_from", "return_label" ];
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    var val = p.getValue(key, null);
    if (val != null) {
      params[key + "_back"] = val;
    }
  }
  return params;
}


/*
 * Removes a single class from the .className attribute of a object.
 *
 * node - The DOM node with the .className attribute to adjust.
 * cname - The class name to remove from the .className attribute.
 */

NstDom.removeClass = function(node, cname) {
  // If nothing set, then nothing to remove
  if ((typeof(node.className) != typeof("")) || (node.className == "")) {
    return;
  }

  // Build new class list, but leave off the one we are trying to remove
  var ca = node.className.split(" ");
  var cstr = "";

  for (var i = 0; i < ca.length; i++) {
    // Build class list, skip if matching class to remove or ""
    if ((ca[i] != cname) && (ca[i] != "")) {
      if (cstr.length > 0) {
        cstr = cstr + " ";
      }
      cstr = cstr + ca[i];
    }
  }
  // Set newly computed class name list
  node.className = cstr;
}

/*
 * Adds a single class to the .className attribute of a object.
 *
 * node - The DOM node with the .className attribute to adjust.
 * cname - The class name to add to the .className attribute.
 */

NstDom.addClass = function(node, cname) {
  // If nothing set, just new class name
  if ((typeof(node.className) != typeof("")) || (node.className == "")) {
    node.className = cname;
    return;
  }

  // Build new class list, but leave off the one we are trying to remove
  var ca = node.className.split(" ");

  for (var i = 0; i < ca.length; i++) {
    // If already present, just return (no need to add)
    if (ca[i] == cname) {
      return;
    }
  }

  // Not found - add to end of className attribute
  node.className = node.className + " " + cname;
}

/*
 * Update visibility of DOM node (set display to "none" or "block").
 *
 * node - DOM node to change visibility of.
 *
 * visible - Pass boolean true to show, boolean false to hide, or
 * string "toggle" to change to opposite state. */

NstDom.setVisible = function(node, visible) {
  if (visible == "toggle") {
    if (node.style.display == "none") {
      node.style.display = "block";
    } else {
      node.style.display = "none";
    }
  } else if (visible == true) {
    node.style.display = "block";
  } else {
    node.style.display = "none";
  }
}   

/*
 * Returns whether a DOM node is currently visible or not.
 */

NstDom.isVisible = function(node) {
  return (node.style.display != "none");
}

/* NstDom.padLeading()
 *
 *   num     - A number or "string" to pad...
 *   len     - Returned string is at least this value in
 *             length with a leading pad fill...
 *   padchar - Optional pad character - defaults to zero (0)...
 *
 *   Returns a leading padded number (num) at least length (len) in size... */
NstDom.padLeading = function(num, len, padchar) {
  var pchar = '0';
  if (padchar != null) {
    pchar = padchar;
  }
  var str = '' + num;
  while (str.length < len) {
      str = pchar + str;
  }
  return str;
}

/**
 * Appends a YouTube video within the page.
 *
 * NOTE: If you have a 1280x720 HD video, try setting width to 640
 * and height to 360 for a "nicely" scaled version in the page.
 *
 * node - DOM node (or node ID) to append to.
 * youTubeId - The unique ID assigned to your YouTube video.
 * width - The width (in pixels) you'd like your video to be.
 * height - The height (in pixels) you'd like your video to be.
 */

NstDom.appendYouTube = function(node, youTubeId, width, height) {
  width = width ? width : 640;
  height = height ? height : 360;

  // Compensate for size of control bar
  var controlBarHeight = 25;
  height += controlBarHeight;

  var data = "https://www.youtube.com/v/" + youTubeId + "&fs=1";

  // IE struggles using the standard DOM method for some reason
  if (isIE() || window.isIE9) {
    var ieText = '<object type="application/x-shockwave-flash"'
      + ' data="' + data + '" width="' + width + '" height="' + height + '">"'
      + '<param name="movie" value="' + data + '" />' 
      + '<param name="wmode" value="transparent" />'
      + '<param name="allowfullscreen" value ="true" />'
      + '<embed src="' + data + '" type ="application/x-shockwave-flash"'
      + ' wmode="transparent" allowfullscreen="true"'
      + ' width="640" height="385" />'
      + '</object>';
    NstDom.getNode(node).innerHTML = ieText;
    return;
  }

  // Other more DOM-compliant browsers (Firefox and Chrome)

  var o = document.createElement("object");
  o.className = "YouTube";
  o.setAttribute("type", "application/x-shockwave-flash");
  o.setAttribute("data", window.location.protocol + "//www.youtube.com/v/" + youTubeId +  "&fs=1");
  o.setAttribute("width", width.toString());
  o.setAttribute("height", height.toString());

  // Append object to node
  o.appendChild(embed);
  NstDom.getNode(node).appendChild(o);

}

/**
 * Builds a graphical feature matrix of images.
 *
 * choices - Array of all possible choice entries we can display in
 * the feature matrix. Each entry must have the following properties:
 *
 *   id - Unique ID associate with feature (used for tooltip)
 *   title - Text title to appear below image
 *   src - URL where image source can be found
 *   url - URL where to go to if user clicks on feature
 *
 * params - Set of properties defining how to form the matrix.
 *
 *   nrows - Number of rows to make the matrix.
 *   ncols - Number of columns to make in the matrix.
 *   hgap - Horizontal gap (in pixels) between each image.
 *   vgap - Veritcal gap (in pixels) between each image.
 *   theight - Height for title area in pixels (or 0 to omit title).
 *   classPrefix - prefix for class names for created entities ("feature")
 *
 * return DOM node containing a "image grid" of clickable features.
 */

NstDom.createFeatureMatrix = function(choices, params) {
  var node = document.createElement("div");
  node.className = params.classPrefix + "Matrix";

  //
  // Let's create our feature matrix from the set of choices
  //

  var n = choices.length;
  var nrows = params.nrows;
  var ncols = params.ncols;

  // If more possible choices then cells to display in, "shuffle the deck"
  if ((nrows * ncols) < n) {
    for (var i = 0; i < n; i++) {
      var j = Math.floor(Math.random() * n);
      var old = choices[i];
      choices[i] = choices[j];
      choices[j] = old;
    }
  }

  var rows = new Array(nrows);
  var i = 0;

  for (var row = 0; row < nrows; row++) {
    var cols = new Array(ncols);
    rows[row] = cols;
    for (var col = 0; col < cols.length; col++) {
      if (i < n) {
        cols[col] = choices[i];
        i++;
      } else {
        cols[col] = false; // Leave column empty (no more choices)
      }
    }
  }

  // Width and height of images in grid
  var iwidth = params.iwidth;
  var iheight = params.iheight;

  // Gap dimensions (in pixels) between entries in grid
  var gwidth = params.hgap;
  var gheight = params.vgap;

  var colsMax = 0;

  // Insertion location for next entry
  var y = 0;
  var x = 0;

  // Create the grid
  for (var row = 0; row < rows.length; row++) {
    var rowInfo = rows[row];
    var cols = rowInfo.length;
    if (cols > colsMax) {
      colsMax = cols;
    }

    // Update insertion points for next row (add gap if not first row, reset x)
    if (row != 0) {
      y += gheight;
    }
    var x = 0;

    // Reset max height of cell in row
    var maxHeight = 0;

    for (var col = 0; col < cols; col++) {
      var info = rowInfo[col];
      if (!info) {
        // Safely skip empty cells
        continue;
      }
      // Add gap if not first column
      if (col != 0) {
        x += gwidth;
      }

      // Create container for cell entry in matrix
      var cnode = document.createElement("div");
      cnode.className = params.classPrefix + "MatrixCell";
      cnode.link = info.link;
      cnode.id = info.id;
      cnode.onclick = function(event) {
        window.open(this.link, '_blank');
      }
      cnode.onmouseover = function(event) {
        NstDom.showToolTip(this, event, this.id);
      }

      // Add image to cell container
      var img = document.createElement("img");
      img.className = params.classPrefix + "MatrixCellImg";
      img.src = info.src;
      img.style.width = "" + iwidth + "px";
      img.style.height = "" + iheight + "px";
      cnode.appendChild(img);

      // Add title to cell container
      if (params.theight > 0) {
        var title = document.createElement("div");
        title.className = params.classPrefix + "MatrixCellTitle";
        title.appendChild(document.createTextNode(info.title));
        cnode.appendChild(title);
      }

      // Add cell container to the feature matrix
      cnode.style.top = y.toString() + "px";
      cnode.style.left = x.toString() + "px";
      cnode.style.width = "" + iwidth + "px";
      var cheight = iheight + params.theight;
      cnode.style.height = "" + cheight + "px";
      node.appendChild(cnode);

      // Move (x) to right to compensate for width
      x += iwidth;

      // Keep track of cell with maximum height on row
      if (cheight > maxHeight) {
        maxHeight = cheight;
      }
    }
    // Update y for next row
    y += maxHeight;
  }

  // Set final size
  var twidth = (colsMax * iwidth);
  if (colsMax > 1) {
   twidth += ((colsMax - 1) * gwidth);
  }
  node.style.width = "" + twidth + "px";

  node.style.height = "" + y + "px";

  return node;
}

/**
 * Returns a new Z-Index value to put window on top of all other windows.
 */

NstDom.getTopZIndex = function() {
  // If using DOM Lib API, use their master zIndex
  if (window.domLib_zIndex) {
    window.domLib_zIndex++;
    return window.domLib_zIndex;
  }

  // Otherwise, use the once specific to the NstDom class
  if (!NstDom._ZIndex) {
    NstDom._ZIndex = 200;
  }
  return NstDom._ZIndex++;
}
/**
 * Singleton to help dynamically load JavaScript, CSS, and "libraries".
 *
 * This class provides the following JavaScript and CSS loaders:
 *
 * NstLoad.loadJavaScript(uri);
 * NstLoad.loadCss(uri);
 *
 * This class also provides the concepts of "libraries" where complete
 * sets of JavaScript and CSS files can be loaded as required. For
 * example, if you want to include a NstMap object on a NST WUI page:
 *
 * NstLoad.loadLibrary("NstMap");
 */

NstLoad = {
  /** Private set of libary information (what to load and dependencies). */
  _LibDeps: new Array(),

  /**
   * Sets a library definition for loading a "collection" of CSS and JavaScript.
   *
   * uname - The name to register the library under. Typically chosen
   * to match something defined in a JavaScript file which will be
   * loaded.
   *
   * props - Set of attributes defining the library. All attributes
   * are optional, though it is unlikely you would ever omit all of
   * them. See the bottom of this file for the core library
   * definitions. Here are the properties you can pass:
   *
   *   test: The name of a variable, function, etc that will be defined
   *   once any associated JavaScript is loaded. If omitted, we will
   *   assume we can use the "name".
   *
   *   css: A string containing a URI of a CSS file to load, or a
   *   array of strings if you need to load more than one CSS file.
   *
   *   js: A string containing a URI of a JavaScript file to load, or
   *   a array of strings if you need to load more than one JavaScript
   *   file.
   *
   *   cssHttps: If present, overrides the css property when https
   *   connections are being used. Typically only required for 3rd
   *   party libraries that are being downloaded from the Internet.
   *
   *   jsHttps: If present, overrides the css property when https
   *   connections are being used. Typically only required for 3rd
   *   party libraries that are being downloaded from the Internet.
   *
   *   requires: A optional array of 0 or more other registered
   *   library names which need to be loaded prior to loading this
   *   library.
   */
  addLibrary: function(name, props) {
    NstLoad._LibDeps[name] = props;
  },

  /**
   * Loads the CSS and JavaScript required by a specific library.
   *
   * This method will attempt to load the CSS and JavaScript required
   * by the named library into the page. If the library has already
   * been loaded (or has been scheduled for loading), then nothing
   * will be done, otherwise, code will be inserted into the page to
   * load the necessary CSS and JavaScript required to support the
   * library (including all of it's dependencies).
   *
   * NOTE: This method is smart that it will only let you load a
   * library one time (any subsequent call to load the same library
   * over will be ignored).
   *
   * name - The name that the library was registered under via the
   * NstLoad.addLibrary(name, props) call.
   */
  loadLibrary: function(name) {
    // Make sure "name" is a registered library
    var lib = NstLoad._LibDeps[name];
    if (!lib) {
      // Not a known library
      alert("System error - request to load unknown library: " + name);
      return;
    }

    // See if associated class is present
    try {
      // Get variable or function name to test for presence of to see
      // if the library is loaded
      var t = (lib.test ? lib.test : name)
      if (eval("typeof(" + t + ")") != typeof(undefined)) {
        // If already loaded, then mark as loaded
        lib._Loaded = true;
        return;
      }
    } catch (ignore) { }

    if (lib._Loaded) {
      // Already loaded (or queued to load)
      return;
    }

    // If library has dependencies, make sure we load them first
    if (typeof(lib.requires) == typeof(NstLoad._LibDeps)) {
      for (var i = 0; i < lib.requires.length; i++) {
        NstLoad.loadLibrary(lib.requires[i]);
      }
    }

    // Load CSS files associated with the library
    var css = lib.css;
    if (lib.cssHttps && (window.location.protocol == "https:")) {
      css = lib.cssHttps;
    }
    if (typeof(css) == typeof("")) {
      // Load single CSS source associated with library
      NstLoad.loadCss(css);
    } else if (typeof(css) == typeof(NstLoad._LibDeps)) {
      // Load array of CSS sources associated with library
      for (var i = 0; i < cs.length; js++) {
        NstLoad.loadCss(css[i]);
      }
    }

    // Load JavaScript files associated with the library
    var js = lib.js;
    if (lib.jsHttps && (window.location.protocol == "https:")) {
      js = lib.jsHttps;
    }
    if (typeof(js) == typeof("")) {
      // Load single JavaScript source associated with library
      NstLoad.loadJavaScript(js);
    } else if (typeof(js) == typeof(NstLoad._LibDeps)) {
      // Load array of JavaScript sources associated with library
      for (var i = 0; i < js.length; js++) {
        NstLoad.loadJavaScript(js[i]);
      }
    }
  },

  /**
   * Loads a single JavaScript file.
   *
   * uri - The location of the JavaScript file to load.
   */

  loadJavaScript: function(uri) {
    /*
    var head = document.getElementsByTagName('head')[0];
    var s = document.createElement("script");
    //s.src = uri;
    //s.type = "text/javascript";
    s.setAttribute("src", uri);
    s.setAttribute("type", "text/javascript");
    head.appendChild(s);
    */
    document.write('<script src="' + uri + '" type="text/javascript"' + '></script>');
  },

  /**
   * Loads a single CSS file.
   *
   * uri - The location of the CSS file to load.
   */

  loadCss: function(uri) {
    /*
    var head = document.getElementsByTagName('head')[0];
    var l = document.createElement("link");
    l.setAttribute("href", uri);
    l.setAttribute("rel", "stylesheet");
    l.setAttribute("type", "text/css");
    head.appendChild(l);
    */
    document.write('<link href="' + uri
                   + '" rel="stylesheet" type="text/css"' + '></link>');
  }
  
};

//
// 3rd party library additions
//

// Virtual keyboard
NstLoad.addLibrary("keyboard", {
  test: "VKI_buildKeyboardInputs",
  css: "/nst/css/keyboard.css",
  js: "/nst/javascript/keyboard.js"
});

// Color picker
NstLoad.addLibrary("jscolor", {
  css: "/nst/css/keyboard.css",
  js: "/nst/javascript/jscolor/jscolor.js"
});

// Google Maps JavaScript API 3.0
NstLoad.addLibrary("maps.google.Map", {
  js: "http://maps.google.com/maps/api/js?sensor=false",
  jsHttps: "https://maps-api-ssl.google.com/maps/api/js?sensor=false"
});

//
// NST generic library additions
//

//
// NST WUI Library definitions
//

NstLoad.addLibrary("Stat", {
  js:  "/nst/javascript/stat.js"
});

NstLoad.addLibrary("Nic", {
  js: [ "/nst/javascript/nic.js", "/nst/php/networking/interface-json.php?op=script" ]
});

NstLoad.addLibrary("NstResolve", {
  js: "/nst/javascript/NstResolve.js" }
);

NstLoad.addLibrary("GeoIpDb", { 
  requires: [ "maps.google.Map", "NstResolve" ],
  js: "/nst/apps/geo/GeoIpDb.js"
});

NstLoad.addLibrary("NstMap", {
  requires: [ "GeoIpDb" ],
  css: "/nst/apps/geo/NstMap.css",
  js: "/nst/apps/geo/NstMap.js"
});
/*
 * A JavaScript implementation of the Secure Hash Algorithm, SHA-256, as defined
 * in FIPS 180-2
 * Version 2.2 Copyright Angel Marin, Paul Johnston 2000 - 2009.
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for details.
 * Also http://anmar.eu.org/projects/jssha2/
 */
var hexcase=0;function hex_sha256(a){return rstr2hex(rstr_sha256(str2rstr_utf8(a)))}function hex_hmac_sha256(a,b){return rstr2hex(rstr_hmac_sha256(str2rstr_utf8(a),str2rstr_utf8(b)))}function sha256_vm_test(){return hex_sha256("abc").toLowerCase()=="ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"}function rstr_sha256(a){return binb2rstr(binb_sha256(rstr2binb(a),a.length*8))}function rstr_hmac_sha256(c,f){var e=rstr2binb(c);if(e.length>16){e=binb_sha256(e,c.length*8)}var a=Array(16),d=Array(16);for(var b=0;b<16;b++){a[b]=e[b]^909522486;d[b]=e[b]^1549556828}var g=binb_sha256(a.concat(rstr2binb(f)),512+f.length*8);return binb2rstr(binb_sha256(d.concat(g),512+256))}function rstr2hex(c){try{hexcase}catch(g){hexcase=0}var f=hexcase?"0123456789ABCDEF":"0123456789abcdef";var b="";var a;for(var d=0;d<c.length;d++){a=c.charCodeAt(d);b+=f.charAt((a>>>4)&15)+f.charAt(a&15)}return b}function str2rstr_utf8(c){var b="";var d=-1;var a,e;while(++d<c.length){a=c.charCodeAt(d);e=d+1<c.length?c.charCodeAt(d+1):0;if(55296<=a&&a<=56319&&56320<=e&&e<=57343){a=65536+((a&1023)<<10)+(e&1023);d++}if(a<=127){b+=String.fromCharCode(a)}else{if(a<=2047){b+=String.fromCharCode(192|((a>>>6)&31),128|(a&63))}else{if(a<=65535){b+=String.fromCharCode(224|((a>>>12)&15),128|((a>>>6)&63),128|(a&63))}else{if(a<=2097151){b+=String.fromCharCode(240|((a>>>18)&7),128|((a>>>12)&63),128|((a>>>6)&63),128|(a&63))}}}}}return b}function rstr2binb(b){var a=Array(b.length>>2);for(var c=0;c<a.length;c++){a[c]=0}for(var c=0;c<b.length*8;c+=8){a[c>>5]|=(b.charCodeAt(c/8)&255)<<(24-c%32)}return a}function binb2rstr(b){var a="";for(var c=0;c<b.length*32;c+=8){a+=String.fromCharCode((b[c>>5]>>>(24-c%32))&255)}return a}function sha256_S(b,a){return(b>>>a)|(b<<(32-a))}function sha256_R(b,a){return(b>>>a)}function sha256_Ch(a,c,b){return((a&c)^((~a)&b))}function sha256_Maj(a,c,b){return((a&c)^(a&b)^(c&b))}function sha256_Sigma0256(a){return(sha256_S(a,2)^sha256_S(a,13)^sha256_S(a,22))}function sha256_Sigma1256(a){return(sha256_S(a,6)^sha256_S(a,11)^sha256_S(a,25))}function sha256_Gamma0256(a){return(sha256_S(a,7)^sha256_S(a,18)^sha256_R(a,3))}function sha256_Gamma1256(a){return(sha256_S(a,17)^sha256_S(a,19)^sha256_R(a,10))}function sha256_Sigma0512(a){return(sha256_S(a,28)^sha256_S(a,34)^sha256_S(a,39))}function sha256_Sigma1512(a){return(sha256_S(a,14)^sha256_S(a,18)^sha256_S(a,41))}function sha256_Gamma0512(a){return(sha256_S(a,1)^sha256_S(a,8)^sha256_R(a,7))}function sha256_Gamma1512(a){return(sha256_S(a,19)^sha256_S(a,61)^sha256_R(a,6))}var sha256_K=new Array(1116352408,1899447441,-1245643825,-373957723,961987163,1508970993,-1841331548,-1424204075,-670586216,310598401,607225278,1426881987,1925078388,-2132889090,-1680079193,-1046744716,-459576895,-272742522,264347078,604807628,770255983,1249150122,1555081692,1996064986,-1740746414,-1473132947,-1341970488,-1084653625,-958395405,-710438585,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,-2117940946,-1838011259,-1564481375,-1474664885,-1035236496,-949202525,-778901479,-694614492,-200395387,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,-2067236844,-1933114872,-1866530822,-1538233109,-1090935817,-965641998);function binb_sha256(n,o){var p=new Array(1779033703,-1150833019,1013904242,-1521486534,1359893119,-1694144372,528734635,1541459225);var k=new Array(64);var B,A,z,y,w,u,t,s;var r,q,x,v;n[o>>5]|=128<<(24-o%32);n[((o+64>>9)<<4)+15]=o;for(r=0;r<n.length;r+=16){B=p[0];A=p[1];z=p[2];y=p[3];w=p[4];u=p[5];t=p[6];s=p[7];for(q=0;q<64;q++){if(q<16){k[q]=n[q+r]}else{k[q]=safe_add(safe_add(safe_add(sha256_Gamma1256(k[q-2]),k[q-7]),sha256_Gamma0256(k[q-15])),k[q-16])}x=safe_add(safe_add(safe_add(safe_add(s,sha256_Sigma1256(w)),sha256_Ch(w,u,t)),sha256_K[q]),k[q]);v=safe_add(sha256_Sigma0256(B),sha256_Maj(B,A,z));s=t;t=u;u=w;w=safe_add(y,x);y=z;z=A;A=B;B=safe_add(x,v)}p[0]=safe_add(B,p[0]);p[1]=safe_add(A,p[1]);p[2]=safe_add(z,p[2]);p[3]=safe_add(y,p[3]);p[4]=safe_add(w,p[4]);p[5]=safe_add(u,p[5]);p[6]=safe_add(t,p[6]);p[7]=safe_add(s,p[7])}return p}function safe_add(a,d){var c=(a&65535)+(d&65535);var b=(a>>16)+(d>>16)+(c>>16);return(b<<16)|(c&65535)};/**
 * The NstGrid object is used to represent a "grid container" having 0
 * or more rows with 1 or more columns.
 *
 * colAttrs - An array of attributes to be applied for each
 * column. For example, the following could be used to create a 3
 * column grid:
 *
 * var myGrid = new NstGrid([
 *   { className: "number" },
 *   { align: "left"; valign: "middle" },
 *   { style: "width: 15px; margin: 12px" }
 * ]);
 *
 * Each array entry can contain an associative array of attributes
 * that should be applied to each <td> object that will be created for
 * the respective column.
 */

function NstGrid(colAttrs) {
  if (!colAttrs) {
    throw new Exception("Missing column attributes to NstGrid constructor");
  }
  this._ColAttrs = colAttrs;
  this._Widget = document.createElement("table");
  this._TBody = document.createElement("tbody");
  this._Widget.appendChild(this._TBody);
}

/**
 * Create a new NstGrid object with identical column attributes.
 *
 * colCnt - Number of columns you want for your new NstGrid [1, INF).
 *
 * colAttr - The attributes to apply to EVERY td in the grid. If
 * omitted, defaults to (SAME is computed based on column count):
 *
 * { align: "center", valign: "middle", style: "width: SAME%;" }
 */

NstGrid.createFixed = function(colCnt, colAttr) {
  var colAttrs = new Array();
  if (!colAttr) {
    colAttr = { align: "center", valign: "middle",
                style: "width: " + (100.0 / colCnt) + "%;" };
  }

  for (var i = 0; i < colCnt; i++) {
    colAttrs.push(colAttr);
  }
  return new NstGrid(colAttrs);
}

/**
 * Get the widget (DOM node) which represents the entire grid.
 */

NstGrid.prototype.getWidget = function() {
  return this._Widget;
}

/**
 * Get the number of columns currently in the grid.
 */

NstGrid.prototype.getCols = function() {
  return this._ColAttrs.length;
}

/**
 * Get the number of rows currently in the grid.
 */

NstGrid.prototype.getRows = function() {
  return this._TBody.children.length;
}

/**
 * Get the widget (DOM node) at a particular (row, col) position in the grid.
 *
 * row - Row index in range of [0, getRows() - 1].
 * col - Column index in range of [0, getCols() - 1].
 *
 * Returns widget that is in grid cell (or undefined if none were).
 */

NstGrid.prototype.get = function(row, col) {
  if ((row >= 0) && (row < this.getRows())) {
    return this._TBody.children[row].firstChild;
  }
  return undefined;
}

/**
 * Set the widget (DOM node) at a particular (row, col) position in the grid.
 *
 * row - Row index in range of [0, INF).
 * col - Column index in range of [0, getCols() - 1].
 * widget - New widget to install (or null to "clear" the grid cell).
 *
 * Returns old widget that was in grid cell (or undefined if none were).
 */

NstGrid.prototype.set = function(row, col, widget) {
  // Ignore if invalid location
  if ((row < 0) || (col < 0) || (col >= this.getCols())) {
    return;
  }

  // Expand table (if neccessary) for position desired
  for (var n = this.getRows(); n <= row; n++) {
    // Create new row
    var tr = document.createElement("tr");
    this._TBody.appendChild(tr);

    // Add columns to new row
    var clen = this._ColAttrs.length;

    for (var c = 0; c < clen; c++) {
      var ca = this._ColAttrs[c];
      var td = document.createElement("td");
      tr.appendChild(td);
      // Set column attributes
      for (var a in ca) {
        // Not sure why the handlers need to be set differently than
        // the attributes
        if (a.substring(0, 2) == "on") {
          td[a] = ca[a];
        } else {
          td.setAttribute(a, ca[a]);
        }
        //td[a] = ca[a];
//        NstConsole.echo("Set attribute " + a + " to " + td[a] + " in cell [" + n + ", " + c + "]");
      }
      // NstConsole.echo("Created cell [" + n + ", " + c + "]");
    }
  }

//  NstConsole.echo("Getting cell [" + row + ", " + col + "]  rows: " + this.getRows() + " cols: " + this.getCols());
  var td = this._TBody.children[row].children[col];
  var oldWidget = td.firstChild;

  if (oldWidget) {
    if (widget) {
      td.replaceChild(widget, oldWidget);
    } else {
      td.removeChild(oldWidget);
    }
  } else if (widget) {
    td.appendChild(widget);
  }

  return oldWidget;
}
/*
 * This class is designed in a manner similar to the java.util.Properties
 * Java class (essentially a hash table of key/value pairs). It has
 * some additional features making it useful for the following purposes:
 *
 * - Parsing the "search" parameters from a URL
 *
 * - Creating the "search" parameters for a URL
 *
 * NOTE: This JavaScript file will always have 0 dependencies (you can
 * load and use it without loading any other JavaScript support
 * functions out of the NST).
 */

/* Constructor - create a new instance with no parameters in it. */

function Properties() {
  this.clear();
}

/* Clear all properties (initialize to the empty set). */

Properties.prototype.clear = function() {
  this._Values = [];
}

/* Get the number if key/value pairs in the set.
 *
 * return Number of key/value pairs contained. */

Properties.prototype.size = function() {
  var cnt = 0;
  for (var e in this._Values) {
    cnt++;
  }
  return cnt;
}

/* Set the value to associate with a particular key.
 *
 * key - Key to associate with the value.
 * value - Value to associate with the key.
 */

Properties.prototype.setValue = function(key, value) {
  this._Values[key] = value;
}

/* Get the value associated with a particular key.
 *
 * key - Key to use to lookup value with.
 * defValue - Value to return if key not found.
 *
 * return Value associate with key or 'defValue' passed.
 */

Properties.prototype.getValue = function(key, defValue) {
  var val = this._Values[key];
  if (typeof(val) != "undefined") {
    return val;
  }

  return defValue;
}

/* Get the integer value associated with a particular key.
 *
 * key - Key to use to lookup value with.
 * defValue - Value to return if key not found.
 *
 * return Integer value associate with key or 'defValue' passed.
 */

Properties.prototype.getIntValue = function(key, defValue) {
  var val = parseInt(this._Values[key]);
  if (!isNaN(val)) {
    return val;
  }
  return defValue;
}

/* Get the float value associated with a particular key.
 *
 * key - Key to use to lookup value with.
 * defValue - Value to return if key not found.
 *
 * return Float value associate with key or 'defValue' passed.
 */

Properties.prototype.getFloatValue = function(key, defValue) {
  var val = parseFloat(this._Values[key]);
  if (!isNaN(val)) {
    return val;
  }
  return defValue;
}

/* Get the float value associated with a particular key.
 *
 * key - Key to use to lookup value with.
 * defValue - Value to return if key not found.
 *
 * return true if value found and was either set to boolean true, or
 * was string "true". false if value found and was either set to
 * boolean false, or was string "false". Otherwise 'defValue' is
 * returned.
 */

Properties.prototype.getBooleanValue = function(key, defValue) {
  var val = this._Values[key];
  if (typeof(val) == typeof(true)) {
    return val;
  } else if (typeof(val) == typeof("")) {
    if (val == "true") {
      return true;
    } else if (val == "false") {
      return false;
    }
  }
  return defValue;
}

/* Copy values from a associative array into this property set.
 *
 * a - Associative array to transfer key/value pairs from. */

Properties.prototype.fromArray = function(a) {
  if ((a == null) || (typeof(a) != typeof([]))) {
    throw new Error("Passed non-array value to method");
  }

  for (var key in a) {
    this.setValue(key, a[key]);
  }
}

/* Copy values from a associative array into this property set.
 *
 * Returns associative array of key/value pairs. */

Properties.prototype.toArray = function() {
  var a = [];

  for (var key in this._Values) {
    a[key] = this._Values[key];
  }

  return a;
}

/* Parse key/value parse out of document cookies.
 *
 * The following strategy is used in parsing out key/value pairs from
 * the cookies associated with the document:
 *
 * - If no cookies present, then nothing done.
 *
 * - Looks for key/value pairs in cookie string having form:
 *
 *   key0=val0;key1=val1;...
 *
 * - Only supports keys and values which do not have leading or trailing spaces.
 *
 * cstr - The cookie string (or omit to use document.cookie).
 */

Properties.prototype.fromCookie = function(cstr) {
  if (typeof(cstr) != typeof("")) {
    cstr = document.cookie;
    if (typeof(cstr) != typeof("")) {
      return;
    }
  }
  // Index to start of key
  var ks = 0;

  // Look for end of key equal sign
  for (var ke = cstr.indexOf("=", ks); ke > ks; ke = cstr.indexOf("=", ks)) {
    var key = cstr.substring(ks, ke).trim();

    // Value starts just past equal sign
    var vs = ke + 1;
    // Look for ending semi-colon or end of string for end of value
    var ve = cstr.indexOf(";", vs)
    if (ve == -1) ve = cstr.length;
    var val = cstr.substring(vs, ve).trim();

    // Store the value
    this.setValue(key, val);

    // Move key start just past ";"
    ks = ve + 1;
  }
}

/* Parse key/value parse out of URL address.
 *
 * The following strategy is used in parsing out key/value pairs from
 * a URL string:
 *
 * - If a "?" is found in the passed string, then everything up to and
 *   including the question mark is ignored.
 *
 * - If a "#" is found in the passed string, then the "#" and
 *   everything that follows it is ignored.
 *
 * - All "+" signs are replaces with the space character " ".
 *
 * - Key value pairs are then looked for in the remaining portion of the
 *   string:
 *
 *   [key0=value0[&key1=value1[...]]]
 *
 * - Each key and value extracted is unescapsed using the
 *   decodeURIComponent() method.
 *
 * - Each key/value pair extracted is then added to the collection (or
 *   replaces an existing entry having the same key).
 */

Properties.prototype.fromUrlParams = function(urlStr) {
  var pstr = urlStr;

  // Trim off up to and including "?"
  var pos = pstr.indexOf("?");
  if (pos > -1) {
    pstr = pstr.substring(pos + 1);
  }

  // Trim off "#" and anything after
  var pos = pstr.indexOf("#");
  if (pos > -1) {
    pstr = pstr.substring(0, pos);
  }

  // Replace all plus signs with space character
  pstr = pstr.replace(/\+/g, " ");

  var cnt = 0;

  // Starting position of key
  var kpos = 0;

  // Look for position of next "equal" sign following key
  for (epos = pstr.indexOf("=", 0); (epos != -1) && (epos != 0);
       epos = pstr.indexOf("=", epos + 1)) {

    // Found another key
    cnt++;
    var key = decodeURIComponent(pstr.substring(kpos, epos));

    // See if "&" after "=" (if not, then this is the last parameter)
    var apos = pstr.indexOf("&", epos);
    if (apos == -1) {
      if (epos == (pstr.length - 1)) {
        var val = "";
      } else {
        var val = decodeURIComponent(pstr.substring(epos + 1));
      }
      this.setValue(key, val);
      return cnt;
    }

    // Add this value and continue to next search
    var val = decodeURIComponent(pstr.substring(epos + 1, apos));
    this.setValue(key, val);

    // Start next search just after "&"
    kpos = apos + 1;
    epos = kpos
  }

  return cnt;
}

/*
 * Forms a single string that can be put after the "?" in a URL.
 *
 * This method allows one to create a single string in the form that
 * can be appended after the "?" when building a URL (the "search"
 * component of "window.location").
 *
 * Returns a string like: "x=3&y=hello%20world&z=abc"
 */

Properties.prototype.toUrlParams = function() {
  var search = "";

  // Skip "&" on first key=value
  var prepend = "";

  for (var key in this._Values) {
    // Append "key=value" on first pass and "&key=value" on following passes
    search += (prepend + encodeURIComponent(key) + "="
               + encodeURIComponent(this.getValue(key)));

    // Insert a "&" before we append next value
    prepend = "&";
  }

  return search;
}

/*
 * Makes a AJAX request to save the current properties to the bash session.
 *
 * scb - Optional success callback method to invoke (see AjaxRequest
 * class). You can omit or pass null.
 *
 * ecb - Optional error callback method to invoke (see AjaxRequest
 * class). You can omit of pass null. */

Properties.prototype.saveBashSession = function(scb, ecb) {
  var ar = new AjaxRequest();
  ar.setUrl("/nst/cgi-bin/system/session_update.cgi");

  // Set successful content handler (if passed)
  if (scb) {
    ar.setContentHandler(scb);
  }

  // Set error content handler (if passed)
  if (ecb) {
    ar.setErrorHandler(ecb);
  }

  var postData = "";

  var i = 0;
  for (var key in this._Values) {
    val = this._Values[key];

    // Prepare to add next key/value pair
    var next = "";
    if (postData.length > 0) {
      // If not first addition, need leading &
      next = "&";
    }
    // Add next key/value pair as required by session_update.cgi
    next = next + "key" + i + "=" + escape(key)
      + "&value" + i + "=" + escape(val);
    postData += next;
    i++;
  }
  ar.setPostData(postData);

  // Now go make the AJAX request
  ar.doRequest();
}
/* Class to help work with MAC addresses. */

function NstMac(macAddr) {
  this._Mac = NstMac.formatMac(macAddr);
}

/* Get the MAC Address/Host Name associated with the object. */

NstMac.prototype.getMac = function() {
  return this._Mac;
}

/* Formats a MAC address given a starting string.
 *
 * val - Value to format
 *
 * formatted - If "val" is valid, then a string of the form:
 * "xx:xx:xx:xx:xx:xx" will be returned. If "val" is null, then "" is
 * returned. If "val" is invalid, then the invalid string is
 * returned. */

NstMac.formatMac = function(val) {
  if (val == null) {
    // Return empty string if passed null
    return "";
  }

  if (!NstMac.validateMac(val)) {
    // Return original bad string if not valid
    return val;
  }

  // Get six values
  var parts = val.split(/[:-]/);
  if (parts.length != 6) {
    return val;
  }

  // Reformat each numeric value
  var results = "";
  for (var i = 0; i < parts.length; i++) {
    if (i != 0) {
      results = results + ":";
    }

    // Verify its a hex value in the range of 0-255
    var ival = parseInt(parts[i], 16);
    if (isNaN(ival) || (ival < 0) || (ival > 255)) {
      return val;
    }

    // Format to hex string and append (check to see if leading "0" required)
    var sval = ival.toString(16);
    if (ival < 16) {
      sval = "0" + sval;
    }
    results += sval;
  }

  return results;
}

/*
 * Regular expression which should exactly match a MAC address.
 *
 * Used by NstMac.validateMac(mac) method. */

NstMac.validateRegExp = /^([0-9a-fA-F]{1,2}[:-]){5}[0-9a-fA-F]{1,2}$/

/*
 * Verify that a string contains a valid numeric MAC address.
 *
 * val - String to be checked.
 *
 * Returns true if val has the form of D.D.D.D and each D
 * is a integer value in the range of 0-255 (like: "192.168.1.2").
 */

NstMac.validateMac = function(val) {
  if (typeof(val) != typeof("")) {
    return false;
  }

  return (0 == val.search(NstMac.validateRegExp));
}

/*
 * Verify a DOM input node contains a valid MAC address.
 *
 * node - DOM input node to validate.
 *
 * return true if user entered valid MAC address.
 */

NstMac.macFieldValidator = function(node) {
  var isValid = NstMac.validateMac(node.value, node);
  NstDom.setValidationState(node, isValid);
  return isValid;
}

/*
 * Create a text input field to enter a MAC address in.
 *
 * defVal - Default (initial) value
 *
 * ttId - ID of registered DOM tooltip (null if you don't want it).
 *
 * len - length of input field visible (optional).
 */

NstMac.createMacInput = function(defVal, ttId, len) {
  var node = document.createElement("input");
  node.className = "NstInputFactory";

  if (typeof(defVal) != typeof("")) {
    defVal = "00:00:00:00:00:00";
  } 
  defVal = NstMac.formatMac(defVal);

  // Save default value as old value and initial value
  node._Default = defVal;
  node.value = node._Old = defVal;

  // Set node validation method
  node._Validate = NstMac.macFieldValidator;

  if (len) {
    node.length = len;
  }

  // At most 17 characters can be entered
  node.maxLength = 17;

  // If DOM tooltip, then add onmouseover handler
  NstDom.setNodeToolTip(node, ttId);

  // Trap events that we want to perform a validation check on
  node.onkeyup = NstDom.checkValueChange;
  node.onblur = NstDom.checkValueChange;

  // Do initial validation of default value
  node._Validate(node);

  return node;
}
/**
 * NstPortList objects are used to manage lists of port numbers.
 *
 * - Construct a new instance.
 * - Use the "add" methods to fill with data.
 * - Use the "contains" method to test whether a port number is contained.
 *
 * Use the static createField method to create a DOM textarea node for
 * validated user input.
 */

function NstPortList() {
  this._PortList = [];
}

/**
 * Clears all of the port numbers currently in the list.
 */

NstPortList.prototype.clear = function() {
  this._PortList = [];
}

/**
 * Adds a new port number to the list of known addresses.
 *
 * port - The port number in the range of [0, 65535] (like "443") to
 * add to the list.
 *
 * return true if valid port number passed and added successfully.
 */

NstPortList.prototype.add = function(port) {
  p = parseInt(port);
  if (isNaN(p) || (p < 0) || (p > 65535) || (port.toString() != p.toString())) {
    return false;
  }

  this._PortList[p] = true;
  return true;
}

/**
 * Test whether a string value contains a valid (parseable) list of port
 * numbers.
 *
 * text - The text string to parse, like: "22 80 443"
 *
 * return true if text string contained a valid port list.
 */

NstPortList.validatePortList = function(text) {
  var l = new NstPortList();
  return l.addList(text);
}

/**
 * Method used for field validation (to verify that data entered is valid).
 *
 * node - DOM node which you want to validate the "value" attribute of
 * (could be a <input> or <textarea> node.
 *
 * true If value contains a valid port list, false if not.
 */

NstPortList.fieldValidator = function(node) {
  var isValid = NstPortList.validatePortList(node.value);
  NstDom.setValidationState(node, isValid);
  return isValid;
}

/**
 * Method used to create a validated input area for the user to enter
 * a port list in.
 *
 * defVal - The default value for the list of port numbers.
 * ttId - The Tool Tip ID to associate with the input field.
 * rows - The number or rows to display.
 * cols - The number of columns to display.
 *
 * return A DOM node that can be inserted into the document.
 */

NstPortList.createField = function(defVal, ttId, rows, cols) {
  var node = document.createElement("textarea");
  node.className = "NstInputFactory";

  if (typeof(defVal) != typeof("")) {
    defVal = "53";
  }

  // Save default value as old value and initial value
  node._Default = defVal;
  node.value = node._Old = defVal;

  // Set node validation method
  node._Validate = NstPortList.fieldValidator;

  // Set rows/cols
  node.rows = (rows ? rows : 3);
  node.cols = (cols ? cols : 132);

  // If DOM tooltip, then add onmouseover handler
  NstDom.setNodeToolTip(node, ttId);

  // Trap events that we want to perform a validation check on
  node.onkeyup = NstDom.checkValueChange;
  node.onblur = NstDom.checkValueChange;

  // Do initial validation of default value
  node._Validate(node);

  return node;
}

/**
 * Add a string containing a list of port numbers to the collection.
 *
 * text - String of port numbers separated by " ,\r\n" characters.
 *
 * return true If all parsed tokens were valid port numbers, false if any
 * were not.
 */

NstPortList.prototype.addList = function(text) {
  var list = text.split(/[ \n\r,]+/);
  var n = list.length;
  var rc = true;
  for (var i = 0; i < n; i++) {
    var port = list[i];
    // Ignore 0 length tokens (leading/trailing delimeter chars)
    if (port.length == 0) {
      continue;
    }
    if (!this.add(port)) {
      rc = false;
    }
  }
  return rc;
}

/**
 * Clears current contents and then adds a string containing a list of
 * port numbers to the collection.
 *
 * text - String of port numbers separated by " ,\r\n" characters.
 *
 * return true If all parsed tokens were valid port numbers, false if any
 * were not.
 */

NstPortList.prototype.setList = function(text) {
  this.clear();
  return this.addList(text);
}

/**
 * Removes a port number from the collection.
 *
 * port The port number to remove.
 */

NstPortList.prototype.remove = function(port) {
  delete this._PortList[port];
}

/**
 * Checks to see if a port number is contained in the collection.
 *
 * port The port number (like 443) to look for.
 */

NstPortList.prototype.contains = function(port) {
  return (this._PortList[port] == true);
}
/*
 * AJAX Class Modeled after the one documented in:
 *
 *   "Build Your Own AJAX WEB APPLICATIONS" by Matthew Eernisse
 *
 *   Class used to perform AJAX requests, basic usage is as follows:
 *
 *   - Construct
 *   - Set Parameters (URL is required - callback handler is typical)
 *   - Request document from server
 *
 *   var ajax = new AjaxRequest();
 *   ajax.setUrl("/time.php");
 *   ajax.setContentHandler(new function(ac) {
 *     var text = ac.getContentText();
 *     if (text) {
 *       alert(text);
 *     }
 *   });
 *   ajax.doRequest();
 */
  
function AjaxRequest() {
    /* Private data - we might change internal names. */
    this._Url = null;
    this._Debug = false;
    this._Request = null;
    this._Method = 'get';
    this._Async = true;
    this._Status = null;
    this._StatusText = '';
    this._PostData = null;
    this._ReadyState = null;
    this._Content = null;
    this._ContentType = "application/x-www-form-urlencoded; charset=UTF-8";
    this._ContentHandler = null;
    this._ErrorHandler = null;
    this._ForceMimeType = null;
    
    // -1 - Unknown (unable create XMLHttpRequest)
    // 0 - XMLHttpRequest (Firefox, IE7, ...)
    // 1 - Recent version of IE (but not IE7)
    // 2 - Old version of IE
    this._RequestType = -1;

    // For methods defined below
    var self = this;

    /* setUrl(url) - Set URL to use when making request. */

    this.setUrl = function(url) {
	self._Url = url;
    }

    /* getUrl() - Get URL used making request. */

    this.getUrl = function() {
	return self._Url;
    }

    /* setPostData(s) - Set data to post back to server as string.
     *
     * s - String containing information to post.
     * [ctype] - Optional Content-Type (only if non-standard post). */

    this.setPostData = function(s, ctype) {
      if (s) {
        self._PostData = s;
	self._Method = 'post';
	if (ctype) {
	    self._ContentType = ctype;
	}
      } else {
        self._PostData = null;
	self._Method = 'get';
      }
    }

    /* setPostParameters(a) - Builds a string of key value pairs from the
     * passed associative array and passes it to setPostData().
     *
     * a - Associative array. */

    this.setPostParameters = function(a) {
      var str = false;
      for (k in a) {
	if (str) {
          str += "&" + escape(k) + "=" + escape(a[k]);
	} else {
          str = escape(k) + "=" + escape(a[k]);
	}
      }
      self.setPostData(str);
    }

    /* setContentHandler(f) - Set callback handler to invoke when
     * content is returned from server. Will be invoked as: f(this)
     * (where 'this' is this AjaxRequest object). */

    this.setContentHandler = function(f) {
	self._ContentHandler = f;
    }

    /* setErrorHandler(f) - Set callback handler to invoke when an
     * error occurs when we make our request to the server. Will be
     * invoked as: f(this, emsg) (where 'this' is this AjaxRequest
     * object and emsg is the exception). */

    this.setErrorHandler = function(f) {
	self._ErrorHandler = f;
    }

    /* getContentText() - Get ASCII text content from last request (returns
     * null if no text content available). */

    this.getContentText = function() {
	if (self._Request != null) {
	    return self._Request.responseText;
	}
	return null;
    }

    /* getContentXml() - Get XML content from last request (returns
     * null if no XML content available). */

    this.getContentXml = function() {
	if (self._Request != null) {
	    return self._Request.responseXML;
	}
	return null;
    }
    
    /* Get access to the raw XMLHttpRequest object used for requests. 
    *
    * @return Reference to XMLHttpRequest object or false if not
    * supported by browser.
    */

    this.getXMLHttpRequest = function() {
	// IE does not seem to permit re-use
	if (!self._Request) {
	    try {
		// Recent browser (Firefox, IE7, etc)
		self._Request = new XMLHttpRequest();
		self._RequestType = 0;
	    } catch (e) {
		try {
		    // Older version of IE 
		    self._Request = new ActiveXObject('MSXML2.XMLHTTP');
		    self._RequestType = 1;
		} catch (e) {
		    try {
			// Real Old version of IE
			self._Request = new ActiveXObject('Microsoft.XMLHTTP');
			self._RequestType = 2;
		    } catch (e) {
  	              if (window.NstConsole && NstConsole.debugLevel(9)) {
	                NstConsole.echoExcpetion(e);
	              }
		      return false;
		    }
		}
	    }
	}
	return self._Request;
    }

    /* doRequest() - Performs the actual request.
    *
    * @return true if request was made. */
    
    this.doRequest = function() {
	var req = self.getXMLHttpRequest();
	if (!req) {
	    if (self._Debug) {
		alert("Failed to create a XMLHttpRequest object!");
	    }
	    return false;
	}

        // Get start time of request
        req.startTime = new Date();

	// Request document from server (NOTE: req.open() is done
	// BEFORE setting onreadystatechange to work around a issue in
	// IE so that the XMLHttpRequest object can be reused - see
	// XMLHttpRequest in WikiPedia for more info).
	if (window.NstConsole && NstConsole.debugLevel(9)) {
	  NstConsole.echo("AJAX " + self._Method
			  + " Request for: " + self._Url);
	}
	req.open(self._Method, self._Url, self._Async);

	// Install callback handler

	req.onreadystatechange = function() {
	    if (self._Request.readyState == 4) {
		var status = self._Request.status;

		if (self._Debug) {
		    alert("XMLHttpRequest Ready State 4, Server Status: "
			  + status);
		}

		if ((status >= 200) && (status < 299)) {
		    if (self._ContentHandler != null) {
			self._ContentHandler(self);
		    }
		} else {
		    if (self._ErrorHandler != null) {
			var emsg = self._Request.statusText
			  + " (" + status + ")";
			self._ErrorHandler(self, emsg);
		    }
		}
	    }
	}
      
	// Do POST or GET data if necessary
	if ((self._Method == 'post') && (self._PostData != null)) {
	    req.setRequestHeader("Content-Type", self._ContentType); 
	    if (window.NstConsole && NstConsole.debugLevel(9)) {
	      NstConsole.echo("Posting: " + self._PostData);
	    }
	    req.send(self._PostData);
	} else if (self._Method == 'get') {
	    req.send(null);
	} else {
	    if (window.NstConsole && NstConsole.debugLevel(1)) {
		NstConsole.echo("***ERROR*** AJAX method (" + self._Method
				+ ") for (" + self._Url
				+ ") is not \"get\" or \"post\".");
	    }
	}

    }

}

/* Attempts to extract a JavaScript object from a JSON response from the server.
 *
 * text - The response text from the server (a JSON string).
 *
 * returns A JavaScript object (or null if not available). */

AjaxRequest.extractJSON = function(text) {

  try {
    var obj = eval(text);
    if (window.NstConsole && NstConsole.debugLevel(9)) {
      NstConsole.echo("Received JSON response:\n\n" + text + "\n\n");
      NstConsole.var_dump(obj);
    }
    return obj;
  } catch (e) {
    if (window.NstConsole && NstConsole.debugLevel(8)) {
      var eclass = "nstConsoleErrorOutput";
      NstConsole.echo("Error processing JSON response:\n\n"
		      + text + "\n\n",
		      false, eclass);
      NstConsole.var_dump(e, eclass);
    }
  }
  return null;
}

/* Make a single request for a JSON object.
 *
 * url - URL to make the request from.
 *
 * cb - Callback handler if you want to process the response (will be
 * passed a single JavaScript object representing the JSON response
 * from the server).
 *
 * params - Associative array of any parameters to pass to the request
 * (use this to avoid putting parameters in your URL). */

AjaxRequest.ajaxRequestJSON = function(url, cb, params) {
  // Build new AjaxRequest
  var ar = new AjaxRequest();
  ar.setUrl(url);

  // Set post data (if passed)
  if (params) {
    var post = "";
    for (var param in params) {
      var prepend = (post.length > 0) ? "&" : "";
      post += prepend + escape(param) + "=" + escape(params[param]);
    }
    ar.setPostData(post);
  }

  // Add a JSON handler
  ar._JsonHandler = false;
  if (cb) {
    ar._JsonHandler = cb;
  }

  // Set regular handler to try and extract JSON and then call JSON handler
  ar.setContentHandler(function(ac) {
    if (ac._JsonHandler) {
      var text = ac.getContentText();
      var obj = AjaxRequest.extractJSON(text);

      if (obj) {
	ac._JsonHandler(obj);
	// Don't need Object anymore
	delete obj;
      }
    }

    // Don't need AjaxRequest object anymore
    delete ac;
  });

  if (window.NstConsole && NstConsole.debugLevel(9)) {
    NstConsole.echo("Requesting JSON object from: " + url,
                    false, "nstConsoleEvalOutput");
  }
  ar.doRequest();
}

