
/*
 *        JavaScript Copyright (c) NGender Consulting Group 2003, 2004
 *            partners 'at' NGender.net
 *
 * You may use this code if you include this block comment unchanged.
 * This code is of Beta quality; use at your own risk without warranty.
 *
 * Blessings,
 *	jgd 'at' NGender.net
 *
 */

/* core.js - lowest level NGender JavaScript library
 * All other NGender JavaScript libraries require this one.
 *
 */

// Very primitive functions for converting function arguments objects
// which are almost arrays, into real arrays.

// I use the name ra to mean an array or argument object.

function ng_copy_ra(ra, target_array) {
  var array = target_array ? target_array : new Array();
  return array.concat.apply(array, ra);
}

// Convert arrayish ra (array or arguments object)
// into a string by joining its elements, converted
// to strings, separated by given delimiter.
// Starting index defaults to 0, delimiter to "\n".
function ng_join(ra, index, delimiter) {
  var array = ra.join ? ra : ng_copy_ra(ra);
  var slice = index ? array.slice(index, ra.length) : array;
  return slice.join(delimiter ? delimiter : "\n");
}

// Error Handling

// Presents joined arguments as message for operator.
function ng_alert() {
  alert(ng_join(arguments));
  return null;
}

// Presents joined arguments as message for operator
// and throws same to prevent continuation after error.
function ng_error() {
  var msg = ng_join(arguments); 
  ng_alert(msg);
  throw new Error(msg);
}

// ng_queue queues up functions (with their arguments) to be
// called when an event is delivered, e.g., onload and
// onresize events.
// If the first argument is the string "document" it will
// be replaced by the value of window.document.

function ng_queue(name) {
  this.name = name;
  this.queue = new Array();
}
ng_queue.prototype.put_first = function(f, args) {
  if (typeof f != "function")
    ng_error("ng_queue: trying to put non-function on q " + this.name);
  this.queue.unshift(ng_copy_ra(arguments));
};
ng_queue.prototype.put = function(f, args) {
  if (typeof f != "function")
    ng_error("ng_queue: trying to put non-function on q " + this.name);
  this.queue.push(ng_copy_ra(arguments));
};
ng_queue.prototype.call_them = function() {
  for (var i = 0; i < this.queue.length; i++) {
    var args = this.queue[i];
    if (args.length == 0) {
      ng_error("ng_queue(" + this.name + "): queue element " + i + " is empty!");
    } else {
      var f = args.shift();	// take the function off
      if (typeof f != "function")
	ng_error("ng_queue(" + this.name + "): expected function, got " + typeof f + ":" + String(f));
      if (args[0] == "document")
	args[0] = document;
      f.apply(window, args);
      args.unshift(f);		// put the function back
    }
  }
};
ng_queue.load = new ng_queue("load");
ng_queue.resize = new ng_queue("resize");


// Simple utility to call a unary function multiple times
// on each of a list of values with a given "this" value
// (the "this" value might be something silly, like window)
function ng_for_each(func, the_this) {
  if (typeof the_this != "object")
        ng_error("ng_for_each: expected this to be an object, not a " + typeof the_this);
  for (var i = ng_for_each.length; i < arguments.length; i++)
    ng_for_one(func, the_this, arguments[i]);
}

function ng_for_one(func, the_this, args) {
  if (typeof func != "function")
    ng_error("ng_for_each: expected func to be a function, not a " + typeof func);
  if (typeof args == "object" && args.length) {
    if (func.length != args.length)
      ng_error("ng_for_each: expected func to have arity " + args.length + "not " + func.length);
    func.apply(the_this, args);
  } else {
    if (func.length != 1)
      ng_error("ng_for_each: expected func to have arity 1 not " + func.length);
    func.call(the_this, args);
  }
}
/*
 *        JavaScript Copyright (c) NGender Consulting Group 2003
 *            partners 'at' NGender.net
 *
 * You may use this code if you include this block comment unchanged.
 * This code is of Beta quality; use at your own risk without warranty.
 *
 * Blessings,
 *	jgd 'at' NGender.net
 *
 */

/* ng_type is
 * (1) the constructor for ng_type objects
 *     Given an instance <atype> of prototype ng_type
 *       atype.is(x)       * returns truth of:  x is of type <atype>
 *       atype.assert(x)   * generates error if x is not of type <atype">
 * (2) a namespace for such objects, e.g.:
 *     ng_type.str.is(x) tests x for being of type "string"
 * (3) a namespace for some type-related helper functions
 *
 */

// Here is the constructor:
function ng_type( name, func ) {
  if (typeof name != "string")
    ng_error("ng_type: new type name not a string:", name);
  if (typeof func != "function")
    ng_error("ng_type(" + name + "): typeof func is" + typeof func );
  if (func.length != 1)
    ng_error("ng_type(" + name + "): arity of func is " + func.length );
  this.name = name;
  this.func = func;
}
// Here are the two methods:
ng_type.prototype.is = function(obj) { return this.func(obj); };
ng_type.prototype.assert = function(obj) {
  if (this.func(obj))
    return obj;
  ng_error("Expected type " + this.name + "; got: ", obj);
};
// Here are some useful meta-functions which create type checking functions:
ng_type.mk_func = {
  // type(<type>) returns a function to check if an object "x" os of type <type>
  type: function(type) { return function(x){typeof x == type;} },
  // eq(<value>) returns a function to check if an object "x" is equal to <value>
  eq: function(value) { return function(x){x == value;} },
  // or(<list of types>) returns a function check if "x" is of any of the <list of types>
  or: function() {
    var f = function(x){
      for (var i = 0; i < arguments.length; i++)
	if ( arguments.caller.types[i].is(x) )
	  return true;
      return false;
    };
    f.types = ng_copy_ra(arguments);
    return f;
  },
  // has(list of property names) returns function checking
  // if "x" is an object having all of those properties.
  has: function() {
    var f = function(x){
      if (typeof x != 'object')
	return false;
      for (var i = 0; i < arguments.length; i++)
	if ( !x[arguments.required_properties[i]] )
	  return false;
      return false;
    };
    f.required_properties = ng_copy_ra(arguments);
    return f;
  },
  // of_proto(constructor) returns function checking if if
  // its argument an object created by that constructor.
  of: function(ctor) { return function(x) { return ctor.prototype.isPrototypeOf(x); } }
};
// Here is the factory and installation method
// make(<name>, <func>)
// (1) creates a new ng_type object of given name and test function
// (2) assigns it to the property ng_type.<name>
ng_type.make = function(name, func) {
  var type = new ng_type(name, func);
  if (ng_type[name])
    ng_error("ng_type.make: name exists:", name);
  ng_type[name] = type;
};
// And here we create lots of handy type objects
ng_type.make("any", function(x) { return true; });
ng_for_each( function(n, t) {ng_type.make(n, ng_type.mk_func.type(t));}, window,
	     ["str", "string"], ["num", "number"], ["bool", "boolean"], ["func", "function"], ["obj", "object"] );
ng_type.make("nil", ng_type.mk_func.eq(null));
ng_type.make("atom", ng_type.mk_func.or(ng_type.str, ng_type.num));
ng_type.make("integer", function(x) {ng_type.num.is(x) && x << 0 == x;});
ng_type.make("nat", function(x) {ng_type.integer.is(x) && x >= 0;});
ng_type.make("int_or_nil", ng_type.mk_func.or(ng_type.integer, ng_type.nil));
ng_type.make("str_or_nil", ng_type.mk_func.or(ng_type.str, ng_type.nil));
ng_for_each( function(t) {ng_type.make(t, ng_type.mk_func.of(t));}, window,
	     "Array", "HTMLDocument" ); // Later: "ng_menu", "ng_slice"
ng_type.make("ra", ng_type.mk_func.has("length"));
ng_type.make("args", ng_type.mk_func.has("length", "callee"));
ng_type.make("args_or_func", ng_type.mk_func.or(ng_type.args, ng_type.func));
/*
 *        JavaScript Copyright (c) NGender Consulting Group 2003
 *            partners 'at' NGender.net
 *
 * You may use this code if you include this block comment unchanged.
 * This code is of Beta quality; use at your own risk without warranty.
 *
 * Blessings,
 *	jgd 'at' NGender.net
 *
 */

/* debug.js
 * NGender Javascript Debugging Code.
 *
 */


// Utilities for extracting printable name and
// header (name plus arguments) of functions.
// These are used for debugging under Mozilla
// based browsers only (converting functions
// to strings is not portable).

ng_debug = {};

function func_to_str(x) {
  switch (typeof x) {
    case "function": return x.toString();
    case "object": return x.callee.toString();
    default: ng_error("func_to_str(x): expected function or arguments object");
  }
  return "function unknown!(unknown!) {unknown!}"
}

function func_head(x) {
  var s = func_to_str(x);
  return s.substring( s.indexOf(" "), s.indexOf(")") + 1 );
}

function func_name(x) {
  var s = func_to_str(x);
  return s.substring( s.indexOf(" ") + 1, s.indexOf("(") );
}

function debug_func(args) {
  return ng_debug[func_name(args)];
}

function func_say(func_args, mesg_args, mesg_func) {
  return mesg_func(
    func_head(func_args.callee) + ":\n"
    + ng_join(mesg_args, mesg_args.callee.length)
    );
}  

function func_mesg(args) {
  return func_say(args, arguments, ng_alert);
}

function func_error(args) {
  return func_say(args, arguments, ng_error);
}

function non_null(args, item) {
  if (item == null) {
    var item_name = arguments.length > arguments.callee.length
      ? ng_join(arguments, arguments.callee.length, ".") : "element";
    func_error(args, item_name + " is null!");
  }
  return item;
}

function get_path(obj) {
  for (var i = arguments.callee.length;
	   obj != null && i < arguments.length;
	   i++)
    obj = obj[arguments[i]];
  return obj;
}

function get_path2(obj, path_str) {
  var path_array = path_str.split(".");
  for (var i = 0; obj != null && i < path_array.length; i++)
    obj = obj[path_array[i]];
  return obj;
}

function set_path2(obj, path_str, new_value) {
  var path_array = path_str.split(".");
  if (path_array.length == 1)
    obj[path_str] = new_value;
  else if (path_array.length > 1) {
    var last = path_array.pop();
    path_array.unshift(obj);
    var sub_object = get_path.apply(this, path_array);
    sub_object[last] = new_value;
  }
  return new_value;
}

function non_null_path(args, obj, path) {
  if (non_null(args, obj, path) == null)
    return null;
  for (var i = arguments.callee.length; i < arguments.length; i++) {
    obj = obj[arguments[i]];
    if ( obj == null ) {
      for (var ii = arguments.callee.length; ii <= i; ii++)
	path += "." + arguments[ii];
      return non_null(args, obj, path);
    }
  }
  return obj;
}

function non_null_path2(args, obj, obj_name, path_str) {
  return non_null_path.apply(this, path_str.split(".").unshift(obj_name).unshift(obj).unshift(args));
}

function func_show(args) {
  if (debug_func(args))
    ng_alert(func_head(args.callee) + ":\n" + ng_join(arguments, 1));
}

function func_enter(args) {
  func_show(args, "args: " + ng_join(args, 0, ", "));
}

function func_leave(args) {
  func_show(args, "returns");
}

function func_return(args, value) {
  func_show(args, "returns: " + String(value));
  return value;
}

// Fetch comma-separated arguments given at end of url after a ?.
// name=value pairs become property of global ng_site object.
// names without values become properties of ng_debug and
// are to be interpreted as the names of functions to trace.
// Fetch tag given at end of url after a #.
// Place it as the property ng_debug.hash
// and interpret it as an initial menu selection
// if it corresponds to a menu item id.
function fetch_url_arguments() {
  if (top.parent.location.hash) {
    ng_debug.hash = top.parent.location.hash.slice(1);
  }
  var query = top.parent.location.search.substring(1);
  var pairs = query.split(",");
  for (var i = 0; i < pairs.length; i++) {
    var pos = pairs[i].indexOf("=");
    if (pos == -1 ) {
      ng_debug[pairs[i]] = true;
    } else {
      var argname = pairs[i].substring(0, pos);
      var value = pairs[i].substring(pos+1);
      ng_site[argname] = unescape(value);
    }
  }
}

fetch_url_arguments();

function node_to_str(node) {
  var s = node.tagName;
  var name = node_name(node);
  if (name) 
    s += " name=" + name;
  if (node.id) 
    s += " id=" + node.id;
  for (var i = arguments.callee.length; i < arguments.length; i++) {
    var path = arguments[i];
    var value = get_path2(node, path);
    if (value != null)
	s += " " + path + "=" + value;
  }
  return "<" + s + ">";
}

function show_object_properties(o, name) {
  var message = "Object " + name + " has properties:";
  for (p in o)
    if (o[p]) {
      message += "\n" + p + " type: " + typeof o[p];
      if (typeof o[p] != "function" && typeof o[p] != "array")
      	message += " value: " + String(o[p]);
    }
  alert(message);
}
/*
 *        JavaScript Copyright (c) NGender Consulting Group 2003
 *            partners 'at' NGender.net
 *
 * You may use this code if you include this block comment unchanged.
 * This code is of Beta quality; use at your own risk without warranty.
 *
 * Blessings,
 *	jgd 'at' NGender.net
 *
 */

/* JavaScript Object Oriented Dynamic HTML API
 * Should support any browser compliant with the W3C Level 1 DOM
 *
 */

/* ng_node node (HTML Element) management system
 *
 * Constructor:
 *	ng_node(doc, node_or_id)
 *		doc must be the document object containing the node
 *		node_or_id is eithe a node, a node id or (with nn4) a unique name
 *	Constructs a high level node object
 * Methods:
 *	top()
 *	left()
 *	height()		// get inner height
 *	width()			// get inner width
 *	height(h)		// set inner height to h
 *	width(w)		// set inner width to w
 *	resize(wt, ht)		// set both inner width and inner height
 *	moveTo(x, y)
 *	moveBy(dx, dy)
 *	setBGColor(color)
 *	visible(true or false)
 *	display(true or false)
 * Static functions
 *	ng_node.make(doc, <id>)	// ng_node.id.<id> = new ng_node(doc, <id>)
 *	ng_node.window.width()
 *	ng_node.window.height()
 *	ng_node.screen.width
 *	ng_node.screen.height
 */


// Here is the constructor:
function ng_node(doc, node_or_id) {
  this.doc = doc;
  this.node = typeof node_or_id == "string"
    ? ng_node.getElementById(doc, node_or_id)
    : node_or_id;
  if (typeof this.node != "object")
    ng_error("ng_node: Node not found!");
  this.node.ng_node = this;  // our node knows us
  this.id = this.node.id ? this.node.id : this.node.name ? this.node.name : "anon";
  this.init();
}
// Here is the factory and installation method
// make(doc, <id>)
// (1) creates a new ng_node object on object of given id
// (2) assigns it to the property ng_node.id.<id>
// (3) returns the new node for convenience
ng_node.id = new Object();
ng_node.make = function(doc, id, alias) {
  if (!alias)
    alias = id;
  if (typeof id != "string")
    ng_error("ng_node.make: id is not a string");
  if (!ng_node.id[id])
    ng_node.id[id] = new ng_node(doc, id);
  if (alias) {
    if (typeof alias != "string")
      ng_error("ng_node.make: alias is not a string");
    ng_node.id[alias] = ng_node.id[id];
  }
  return ng_node.id[id];
};
ng_node.setPixels = function(object, property, value, id) {
  var new_value = (typeof object[property] == "string") ? value + "px" : value;
  if (object[property] != new_value) {
    if (id)
      alert('Before: ' + id + '.' + property + ' = ' + value);
    object[property] = new_value;
    if (id)
      alert('After: ' + id + '.' + property + ' = ' + value);
  }
};
ng_node.prototype.setPixels = function(property, value, id) {
  ng_node.setPixels(this.style, property, value, id);
};
ng_node.prototype.no_method_for = function(method_name) {
  ng_error("ng_node(" + this.node.id + "): No method for " + method_name);
};
ng_node.prototype.height = function(h,extra_h) {
  if (!h)
    return this.getInnerHeight();
  this.setInnerHeight(h,extra_h);
};
ng_node.prototype.width = function(w) {
  if (!w)
    return this.getInnerWidth();
  this.setInnerWidth(w);
};
ng_node.prototype.left = function(x) {
  if (!x)
    return this.getLeft();
  this.setLeft(x);
}
ng_node.prototype.top = function(y) {
  if (!y)
    return this.getTop();
  this.setTop(x);
}
ng_node.prototype.resize = function(w, h) {
  this.setInnerWidth(w);
  this.setInnerHeight(h);
};
ng_node.no_method_for = function(method_name) {
  ng_node.prototype[method_name] = function() {
    this.no_method_for(method_name);
  };
};


ng_node.init = function(doc) {

  ng_node.node_type = {
    element: 1,
    attribute: 2,
    text: 3,
    comment: 8,
    document: 9,
    document_fragment: 11
  };

  ng_node.className = doc.all ? "className" : "class";

  ng_node.css = doc.images && doc.body && doc.body.style;
  ng_node.mozilla = !doc.all;
  ng_node.w3c = ng_node.css && doc.getElementById;
  ng_node.ie4 = ng_node.css && doc.all;
  ng_node.nn4 = doc.images && doc.layers;
  ng_node.ie6 = doc.images && doc.compatMode && doc.compatMode.indexOf("CSS1") >= 0;
  if (ng_debug.sniff)
    alert("css: " + ng_node.css + "\n"
	  + "mozilla: " + ng_node.mozilla + "\n"
	  + "w3c: " + ng_node.w3c + "\n"
	  + "ie4: " + ng_node.ie4 + "\n"
	  + "nn4: " + ng_node.nn4 + "\n"
	  + "ie5: " + ng_node.ie6 + "\n");
  ng_node.screen = {
    width: parseInt(screen.availWidth || screen.width),
    height: parseInt(screen.availHeight || screen.height)
  };
//   if (ng_node.screen) {
//     alert("ng_node.screen now defined");
//   }

  // Define ng_node.id_to_node used by ng_node.getElementById
  if (doc.getElementById)
    ng_node.id_to_node = function(doc, id){return doc.getElementById(id);};
  else if (doc.all)
    ng_node.id_to_node = function(doc, id){return doc.all(id);};
  else if (doc.layers)
    ng_node.id_to_node = function(doc, id){
      var node;
      for (var i = 0; i < doc.layers.length; i++) {
	if (doc.layers[i].name == name)	// is it in this layer?
	  return doc.layers[i]; // then return it; we're done!
	if (doc.layers[i].document.layers.length > 0) // any nested layers?
	  if (node = seekLayer(doc, doc.layers[i].document, name)) // look there
	    return node;	// found it; we're done!
      }
      return null;		// we failed!
    };
  else
    ng_error("ng_node.getElementById: No method available!");
  ng_node.getElementById = function(doc, id) {
    node = ng_node.id_to_node(doc, id);
    if (node == null)		// might check if is actually a node!
      ng_error("ng_node.getElementByNode: No node with id " + id);
    return node;
  };

  var p = ng_node.prototype;

  if (ng_node.css){
    p.init = function() {
      this.style = this.node.style;
    };
    p.moveTo = function(x,y) {
      if (x) this.setLeft(x);
      if (y) this.setTop(y);
    };
    p.setLeft = function(x) {
      this.setPixels("left", x);
    };
    p.setTop = function(y) {
      this.setPixels("top", y);
    };
    p.moveBy = function(dx,dy) {
      if (dx != "undefined")
	setPixels("left", dx + getLeft());
      if (dy != "undefined")
	setPixels("top", dx + getTop());
    };
    p.setBGColor = function(color) {
      this.style.backgroundColor = color;
    };
    p.visible = function(visible) {
      this.style.visibility = visible ? "visible" : "hidden";
    };
    p.display = function(display) {
      this.style.display = display ? "block" : "none";
    };
  } else if (ng_node.nn4) {
    p.moveTo = function(x,y) {
      this.node.moveTo(x,y);
    };
    p.moveBy = function(dx,dy) {
      this.node.moveBy(dx,dy);
    };
  }

  // Otherwise, try our best:
  if (!p.init)
    p.init = function() {
      this.style = this.node;	// liar, liar; pants on fire!
    };
  if (!p.moveTo)
    ng_node.no_method_for("moveTo");
  if (!p.setZIndex)
    p.setZIndex = function(z) {
      this.style.zIndex = z;
    };
  if (!p.setBGColor) {
    this.node.bgColor = color;
  }
  p.getComputedStyle = function(prop) {
    var result;
    if (this.doc.defaultView) {
        var style = this.doc.defaultView;
        var cssDecl = style.getComputedStyle(this.node, "");
        result = cssDecl.getPropertyValue(prop);
    } else if (this.node.currentStyle) {
        result = this.node.currentStyle[prop];
    } else if (this.style) {
        result = this.style[prop];
    } else if (this.doc.images && this.doc.layers) {
        result = this.node[prop];
    }
    if (!result)
      this.no_method_for("getComputedStyle(" + prop + ")");
    return typeof result == "string" ? parseInt(result) : result;
  };
  p.getLeft = function() {return this.getComputedStyle("left");};
  p.getTop = function() {return getComputedStyle("top");};
  p.dimension = function(dim, offsetDim, pixelDim) {
    var result;
    if (result = this.node[offsetDim])
      return result;
    if (this.node.clip && (result = this.node.clip[dim]))
      return result;
    if (result = this.style[pixelDim])
      return parseInt(result);
    if (result = this.node[dim])
      return parseInt(result);
    this.no_method_for("dimension(" + dim + ")");
  };
  p.getInnerHeight = function() {return this.dimension("height", "offsetHeight", "pixelHeight");};
  p.getInnerWidth = function() {return this.dimension("width", "offsetWidth", "pixelWidth");};
  p.setInnerHeight = function(h,extra_h) {
    // extra_h is sum of top margin ht and top and bottom border heights
    // need to put it in conditionally for some browsers
    // if (extra_h) h += extra_h;
    var id, style_id;
    if (ng_debug.height) {
      id = this.node.id;
      style_id = id + ".style";
    }
    this.setPixels("height", h, style_id );
    if (this.node.name == this.node.id)
      ng_node.setPixels(this.node, "height", h, id);
  };
  p.setInnerWidth = function(w) {
    var id, style_id;
    if (ng_debug.width) {
      id = this.node.id;
      style_id = id + ".style";
    }
    this.setPixels("width", w, style_id);
    //    if (this.node.name == this.node.id)
    ng_node.setPixels(this.node, "width", w, id);
  };
};

ng_queue.load.put_first(ng_node.init, "document");

// Return the available content width space in browser window
if (!ng_node.window)
  ng_node.window = {};
ng_node.window.width = function(x) {
  if (x = window.innerWidth) return x;
  var b = document.body;
  if (ng_node.ie6) return b.parentElement.clientWidth;
  if (b && (x=b.clientWidth)) return x;
  ng_error("Cannot compute window width");
};
ng_node.window.height = function(y) {
  if (y = window.innerHeight) return y;
  var b = document.body;
  if (ng_node.ie6) return b.parentElement.clientHeight;
  if (b && (y = b.clientHeight)) return y;
  ng_error("Cannot compute window height");
};

// OK, here are some non-object-oriented useful HTML functions

// Courtesy of http://www.alistapart.com/stories/alternate/
function setActiveStyleSheet(doc, title) {
  var links = doc.getElementsByTagName("link");
  var a, current, desired;
  for (var i=0; a = links[i]; i++)
    if (a.getAttribute("rel").indexOf("style") != -1) {
      if (!a.disabled)	current = a;
      var t = a.getAttribute("title");
      if (t != null && t == title) desired = a;
    }
  if (desired != null && desired.disabled) {
        if (current != null)
          current.disabled = true;
    desired.disabled = false;
  }
}
/*
 *        JavaScript Copyright (c) NGender Consulting Group 2003, 2004
 *            partners 'at' NGender.net
 *
 * You may use this code if you include this block comment unchanged.
 * This code is of Beta quality; use at your own risk without warranty.
 *
 * Blessings,
 *	jgd 'at' NGender.net
 *
 */

/* NGender JavaScript Dynamic Menu System
 *
 */

function max_non_null(x, y) {
  return x == null ? y :
	 y == null ? x :
	 x > y ? x : y;
}

function max_non_null_name(x, y, x_name, y_name) {
  return x == null ? (y == null ? null : y_name) :
	 y == null ? x_name :
	 x > y ? x_name : y_name;
}


// NG Menus are trees of nodes implementing selectable
// items with submenus.  Menu nodes may have a special
// role given by their id attribute.  The fixed roles are:
//	"top"  ->  the top of a menu tree
//	"item"  ->  a selectable item
//	"dir"	->  a submenu
// Any other role within a menu tree but not within
// a submenu will be automatically indexed under that  name in
// the _ng object of the enclosing menu item.
function id_to_menu_role(node) {
  if (!node || typeof node != "object" || !node.id || node.id.indexOf("+") == -1)
    return null;
  var id = node.id.split("+");
  if (id[0] != "menu")
    return null;
  return id[1]; 
}

function menu_role(node) {
  if (!node || typeof node != "object" || !node._ng)
    return null;
  return node._ng.role;
}

function set_property(node, property, new_value) {
  func_enter(arguments);
  var old_value = non_null_path(arguments, node, "node", property);
  if (old_value != new_value)
    node[property] = new_value;
  return func_return(arguments, old_value);
}

// Set an node"s property to one of two values
// according to boolean choice, or flip it if no choice.
function toggle_property(node, property, off, on, choice) {
  if (choice == null)
    choice = non_null_path(arguments, node, "node", property) == off;
  set_property(node, property, choice ? on : off);
  return choice;
}

// non-viewable objects still occupy screen area
function toggle_visibility_property(node, on_or_off) {
  switch (node._ng.visibility) {
  case "none": return null;
  case "display": return toggle_property(node.style, "display", "none", "block", on_or_off);
  default: return toggle_property(node.style, "visibility", "hidden", "visible", on_or_off);
  }
}

// non-visible objects are removed from layout
function toggle_display_property(node, on_or_off) {
  switch (node._ng.visibility) {
  case "none": return null;
  case "visibility": return toggle_property(node.style, "visibility", "hidden", "visible", on_or_off);
  default: return toggle_property(node.style, "display", "none", "block", on_or_off);
  }
}

function toggle_display_property_submenu(node, on_or_off) {
  assert_is_menu_item(arguments, node);
  var submenu = node._ng.submenu;
  if (submenu)
    return toggle_display_property(submenu, on_or_off);
  return false;
}


// called by onload event of body node
// give it the ids of any menu-bases.
function inherit_wealth_onload(the_doc) {
  func_enter(arguments);
  for (var i = arguments.callee.length; i < arguments.length; i++) {
    var base_id = "menu+top+" + arguments[i];
    var the_base = ng_node.getElementById(the_doc, base_id);
    honor_your_ancestors(the_doc, the_base);
    var start_item_id = "menu+item+" + arguments[i];
    var start_item = ng_node.getElementById(the_doc, start_item_id);
    select(start_item);
  }
  func_leave(arguments);
}

ng_queue.load.put(inherit_wealth_onload, "document", "ng-site");


// Give every tagged node under a given menu base
// immediate access to its parent and its base through
// properties of its _ng object.  Give every menu item
// immediate access to any submenu or other special
// descendants - again through properties of its _ng object.
function honor_your_ancestors(the_doc, the_base) {
  var count = 0;
  
  function honor(debug_indent, debug_ancestry, level, node, parent, item) {
    var next_item = item;  // unless we change our mind in the switch
    var next_level = level;  // unless we change our mind in the switch
    count++;
    if (debug_func(arguments)) {
      debug_ancestry += "\n" + debug_indent + node_to_str(node);
      debug_indent += "  ";
      func_mesg(arguments, debug_ancestry);
    }
    node._ng = {doc: the_doc, base: the_base, par: parent};
    var type = id_to_menu_role(node);
    if (type != null)
      node._ng.role = type;
    switch (type) {
    case "top":
      next_level = 0;
      if (ng_debug["menu-top"] != null)
	alert("found the menu top!");
      break;
    case "dir":
      next_level = level + 1;
      if (item != null) item._ng.submenu = node;
      next_item = null;
      break;
    case "item":
      next_item = node;
      break;
    case null:	  break;
    default:
      if (item != null) item._ng[type] = node;
      break;
    }
    for (var child = node.firstChild; child; child = child.nextSibling)
      if (typeof child == "object" && child.tagName != null)
	honor(debug_indent, debug_ancestry, next_level, child, node, next_item);
  }
    
    func_enter(arguments);
  if (id_to_menu_role(the_base) != "top")
    func_error(arguments, "expected menu_role(the_base) == \"top\"");
  honor("", "", 0, the_base, null, null)
  return func_return(arguments, count);
}

function is_descendant_of(descendant, ancestor) {
  for ( ; descendant ; descendant = descendant._ng.par)
    if (descendant == ancestor)
      return true;
  return false;
}
 
function select(node) {
  non_null(arguments, node, "node");
  node.className = "menuItemSelected";
  node._ng.image.src = ng_site.hand.src;
  node._ng.base._ng.current = node;
  var doc = non_null_path(arguments, node, "node", "_ng", "doc");
  resize_page(node);
}

function unselect(node) {
  non_null(arguments, node, "node");
  node.className = "menuItem";
  node._ng.image.src = ng_site.blank.src;
}

function assert_is_menu_item(args, node) {
  if (menu_role(node) != "item")
    func_mesg(args, "node not a menu item.");
}

// mark/unmark the trail from a given menu item to the menu top
function blaze_trail(node, mark) {
  assert_is_menu_item(arguments, node);
  for ( ; menu_role(node) != "top" ; node = node._ng.par)
	node._ng.blaze = mark;
}    

function close_up(node) {
  func_enter(arguments);
  assert_is_menu_item(arguments, node);
  for ( ; menu_role(node) != "top" && !node._ng.blaze; node = node._ng.par)
	if (menu_role(node) == "dir")
	  toggle_display_property(node, false);
  func_leave(arguments);
}    

// returns the number of levels we"ve opened up
function open_up(node) {
  assert_is_menu_item(arguments, node);
  var i = toggle_display_property_submenu(node, true) ? 1 : 0;
  for ( ; menu_role(node) != "top" && !node._ng.blaze; node = node._ng.par)
	if (menu_role(node) == "dir")
	  if (toggle_display_property(node, true))
		i++;
  return i;
}    

// Event propagation control
// caught(event object, boolean value) sets the event.caught status
// caught(event object) tells you if event was already caught
// First responders will get false, everyone else will get true;
function caught(event, value) {
  var old_value = event.caught;
  event.caught = value == null ? true : value;
  return old_value;
}

function menu_base_node(menu_node) {
  return non_null_path(arguments, menu_node, "menu_node", "_ng", "base");
}

function menu_current_node(menu_node) {
  return non_null_path(arguments, menu_node, "menu_node", "_ng", "base", "_ng", "current");
}

function menu_depth(menu_node) {
  func_enter(arguments);
  var node = menu_current_node(menu_node);
  var i = node.style.display == "none" ? 0 : 1;
  for (; menu_role(node) != "top"; node = node._ng.par)
    if (menu_role(node) == "dir")
      i++;
  return func_return(arguments, i - 2);
}

// The next two functions are the only "event" handling functions.

function color_menu_item(event, node) {
  assert_is_menu_item(arguments, node);
  if (caught(event)) return;
  //  non_null_path(arguments, "color_menu_item", node, "node", "style");
  var label = non_null(arguments, node._ng.label, "node label");
  label.style.backgroundColor = event.color;
}

function select_menu_item(event, node) {
  if (event && caught(event)) return;
  func_enter(arguments);
  assert_is_menu_item(arguments, node);
  var doc = non_null_path(arguments, node, "node", "_ng", "doc");
  var current = non_null_path(arguments, node, "node", "_ng", "base", "_ng", "current");
  if (node == current) {
    toggle_display_property_submenu(node);
  } else {
    blaze_trail(node, true);		// mark new path
    unselect(current);			// style it unselected
    if (!is_descendant_of(node, current))
	toggle_display_property_submenu(current, false);
    close_up(current);			// hide what will not be part of new path
    var levels = open_up(node);	// unhide to the new node
    blaze_trail(node, false);	// remove the blazing marks
  }
  select(node);			// makes it current and loads frame
  if (!event) {			// KLUDGE!!!
    var iframe = document.getElementById("MainFrame");
    iframe.src = node._ng.label.href;
    var prefix = 'menu+item+ng-site+';
    var id = node.id.slice(prefix.length);
    top.parent.location.hash = id;
  }
  func_leave(arguments);
}

function menu_link(tag) {
  parent.select_menu_item(null, parent.document.getElementById('menu+item+ng-site+' + tag));
}
/* JavaScript Copyright (c) NGender Consulting Group 2003, 2004
 *      partners 'at' NGender.net
 *
 * You may use this code if you include this block comment unchanged.
 * This code is of Beta quality; use at your own risk without warranty.
 *
 * Blessings,
 *	jgd 'at' NGender.net
 */

/* JavaScript for NGender website index page.
 * This code is also called from within the iframe pages.
 */



var ng_site = {
  event: {
    mouseup: {name: "mouseup", caught: false, color: "white"},
    mousedown: {name: "mousedown", caught: false, color: "#AA6600"},
    mouseover: {name: "mouseover", caught: false, color: "#FFFFAA"},
    mouseout: {name: "mouseout", caught: false, color: "white"},
    resize: {name: "resize", caught: false},
    click: {name: "click", caught: false}
  },
  hand: new Image(16,16),
  blank: new Image(16,16)
};

ng_site.hand.src = "/ngender/include/img/hnd.png";
ng_site.blank.src = "/ngender/include/img/bnk.png";

/* I want to preload these, and an early call  to
 * resize_page from within the menu code is messing
 * that up.  I think I will be able to remove that
 * call and have this work better.
 - the early call is coming from the first iframe page
   which is calling its body onload before the index page
   has finished loading!
 */
function preload_nodes() {
  if (!preload_nodes.id) {
    ng_node.make(document, "Header");
    ng_node.make(document, "Footer");
    ng_node.make(document, "MenuCell");
    ng_node.make(document, "MainRow", "Row");
    // I'd like to compute the Row's width, but Konqueror complains!
    // so let's work with the table itself:
    ng_node.make(document, "BodyTable", "Table");
    ng_node.make(document, "menu+top+ng-site", "Menu");
    ng_node.make(document, "MainCell", "Main");
    ng_node.make(document, "MainFrame");
    preload_nodes.id = ng_node.id;
  }
  return preload_nodes.id;
}

function ng_load_home() {
  var n = preload_nodes();
  n.MainFrame.node.src = "/ngender/Top/home.html";
}
ng_queue.load.put(ng_load_home, "document");


function resize_page() {
  func_enter(arguments);
  var n = preload_nodes();
  var win = ng_node.window;
  var body_padding = 10;
  var extra_height =  0 + 1 + 1 + body_padding * 3;
  var middle_height = win.height() - 80 - 15 - extra_height;
                      var menu_width_fudge = 42 ;
    n.MenuCell.width(n.Menu.width()+menu_width_fudge);
        
  var w = n.Main.width();
  n.Menu.height(middle_height);
  n.MainFrame.resize(w, middle_height);
  
  if (ng_debug.width) {
    alert('Leaving resize_page: ' + n.MainFrame.id + '.width = ' + n.MainFrame.width());
  }
  func_leave(arguments);
}

ng_queue.load.put(resize_page);
ng_queue.resize.put(resize_page);



function alt_index(file, id) {
  /*
  var n = preload_nodes();
  var doc = parent.document;
  if (!file) {
    n.HeadFrame.node.src = alt_index.file;
  } else {
    alt_index.index_file = "/ngender/" + file + "-index.html";
  }
  */
}

alt_lookup = alt_index;

function setStyleSheet(doc) {
  var size = parent.ng_node.screen;
  if (!size) {
    if (ng_debug.debug)
      alert("setStyleSheet: Who called me early?");
    size = {
      width: parseInt(screen.availWidth || screen.width),
      height: parseInt(screen.availHeight || screen.height)
    };
  }
  var screen_size = "medium screen ";
  if (size.width <= 800 || size.height <= 600)
    screen_size = "small screen ";
  else if (size.width >= 1200 || size.height >= 1024)
    screen_size = "big screen ";
  var browser = ng_node.mozilla ? "standard browser" : "internet explorer";
  if (ng_debug.setStyleSheet)
    alert("About to call setActiveStyleSheet with " + screen_size + browser);
  setActiveStyleSheet(doc, screen_size + browser );
  if (ng_debug.setStyleSheet)
    alert("Returned from setActiveStyleSheet");
}
ng_queue.load.put(setStyleSheet, "document");

function ng_call_load_queue() { ng_queue.load.call_them(); };
function ng_call_resize_queue() { ng_queue.resize.call_them(); }

function initialFrame(doc) {
  if (ng_debug.hash) {
    var id = 'menu+item+ng-site+' + ng_debug.hash;
    var item = doc.getElementById(id);
    select_menu_item(null, item);
  }
}

ng_queue.load.put(initialFrame, "document");
