/*
 *  Standard JavaScript © 1996-2010, Horus Web Engineering Ltd
 *
 *  $Id: horus.js,v 1.233 2010-08-04 11:47:45 horus Exp $
 *
 *  licensed under the terms of the GNU Lesser General Public License:
 *    http://www.opensource.org/licenses/lgpl-license.php
 *
 */

if (!window.horus) window.horus={ window: window };

// unreliable browser detects... don't use for anything critical
//
horus.opera=window.opera || navigator.userAgent.indexOf('Opera')>=0;

horus.khtml=
  !horus.opera &&
  (navigator.userAgent.indexOf('Safari')>=0 || navigator.userAgent.indexOf('KHTML')>=0);

horus.gecko=!horus.opera && !horus.khtml && navigator.userAgent.indexOf('Gecko')>=0;
horus.ie=!horus.opera && document.all;
horus.iefix=[];

if (horus.ie) {
  horus.iewin=navigator.userAgent.indexOf('Windows')>=0;
  horus.iemac=!horus.iewin;
  horus.ie=navigator.userAgent.match(/MSIE (\d+\.\d+)/);
  if (horus.ie) horus.ie=horus.ie[1];
  if (horus.ie) horus.ie=Number(horus.ie);
  horus.ieold=horus.iewin && horus.ie<7;
  horus.brokenDOM=horus.iewin && horus.ie<8;
} else
  horus.iewin=horus.iemac=horus.ieold=horus.brokenDOM=false;

horus.$goCheck=false;	// kludgearound for IE/Win and Opera weirdness


horus.script={
  fn:
    function ( script ) {
      if (script.indexOf('.')<0) script=script+'.js';
      if (script.charAt(0)!='/' && script.indexOf(':')<0) script='/common/js/'+script;
      return script;
    },

  wrap:
    function ( fnin, scriptin ) {
      var script=horus.script.fn(scriptin);
      var fn=fnin;
      var wrap=window;
      var component;
      var point=fn.indexOf('.');

      if (point>=0) {
	if (point==0) fn='horus'+fn;
	fn=fn.split('.');

	while (fn.length>1) {
	  component=fn.shift();
	  wrap=wrap[component];
	}

	fn=fn[0];
      }

      wrap[fn]=
        function () {
	  return horus.script.autoloader(script, wrap, fn, arguments, this);
        };

    },

  loaded:
    function ( script, state ) {
      script=horus.script.fn(script);
      if (state==null) state=2;
      var oldstate=horus.script.state[script];
      if (oldstate==null) oldstate=0;
      if (oldstate>=state) return oldstate;
      horus.script.state[script]=state;

      if (state==2) {
	var pending=horus.script.pending[script];

	if (pending) {
	  delete horus.script.pending[script];

	  while (pending.length) {
	    var action=pending.shift();
	    action.wrap[action.fn].apply(action.object, action.argv);
	  }
	}
      }

      return null;
    },

  loading:
    function ( debug ) {
      var loading=[];

      for (var script in horus.script.state)
	if (horus.script.state[script]==1) loading.push(script);

      if (!loading.length)
	loading=null;
      else if (debug)
	alert(debug+' [ '+loading.join(', ')+' ]');

      return loading;
    },

  loadOne:
    function ( script ) {
      if (horus.script.loaded(script, 1)) return;

      if (horus.script.broken) {
	document.writeln('<script type="text/javascript" src="'+script+'"></script>');	 
	horus.script.loaded(script);
      } else {
	var s=document.createElement('script');
	s.type='text/javascript';
	s.src=script;
	s.async=true;

	if (horus.brokenDOM)
	  s.onreadystatechange=
	    function () {
	      if (this.readyState=='loaded' || this.readyState=='complete')
		horus.script.loaded(script);

	    };

	else
	  s.onload=function () { horus.script.loaded(script) };

	document.head.appendChild(s);
      }
    },

  loader:
    function ( argv, offset ) {
      for (var i=offset; i<argv.length; i++)
	horus.script.loadOne(horus.script.fn(argv[i]));

    },

  isLoaded: function ( script ) { return horus.script.loaded(script, 0) },
  load:     function () { horus.script.loader(arguments, 0) },
  loadIf:   function ( condition ) { if (condition) horus.script.loader(arguments, 1) },

  autoloader:
    function ( script, wrap, fn, argv, object ) {
      script=horus.script.fn(script);
      if (!object) object=window;
      if (horus.script.isLoaded(script)==2) return wrap[fn].apply(object, argv);
      if (!horus.script.pending[script]) horus.script.pending[script]=[];

      horus.script.pending[script].push
        ({ wrap: wrap, fn: fn, argv: argv, object: object });

      horus.script.loadOne(script);
      return false;
    },

  autoload:
    function () {
      if (!document.head) document.head=document.getElementsByTagName('head')[0];

      for (var a=0; a<arguments.length; a+=2) {
	var script=arguments[a];

	if (horus.script.broken)
	  horus.script.load(script);
	else {
	  var fn=arguments[a+1];

	  if (fn instanceof Array)
	    for (var f=0; f<fn.length; horus.script.wrap(fn[f++], script));
	  else
	    horus.script.wrap(fn, script);

	}
      }
    },

  broken: horus.ieold,
  state: {},
  pending: {}
};

horus.script.autoload
  ('btt',         '.btt',
   'call',        [ '.call', '.setTimeout' ],
   'controls',    '.appendControl',
   'defer',       [ '.defer', '.defer.assign' ],
   'dom',         [ '.insertChild', '.childText', '.replaceHTML', '.setClass' ],
   'matchheight', [ '.matchHeight', '.matchHeight.lock', '.matchHeight.unlock' ],
   'testurl',     '.testurl',
   'trace',       [ '.trace', '.alert', '.alertIf', '.timer', '.counter' ],
   'visible',     [ '.visibility', '.visible', '.visible.put' ]);


// implement missing Array methods in Windows IE 5.0
//
horus.script.loadIf(!Array.prototype.push, 'ie-win-5.0.js');

if (horus.iewin) {
  // fix broken getElementById method
  //
  horus.script.loadIf(horus.brokenDOM, 'ie-getelement-fix.js');

  // kludge in PNG alpha transparency for 5.5 <= IE/Win < 7
  //
  horus.script.loadIf(horus.ie>=5.5 && horus.ie<7, 'pngfix.js');

  // min-width hack code for ie<7
  //
  horus.script.loadIf(horus.ie<7, 'minwidth.js');

  // Chris Ridings' Google Autolink blocker
  //
  horus.script.load('autoblink.js');
}

// form/page processing symbolic constants as per Perl and ColdFusion
//
horus.FORM_ENTRY    = 0;
horus.FORM_SUBMIT   = -1;
horus.FORM_REFRESH  = -2;
horus.FORM_RESET    = -3;
horus.FORM_NEW      = -4;
horus.FORM_ADD      = -5;
horus.FORM_DELETE   = -6;
horus.FORM_EDIT     = -7;
horus.FORM_NEXT     = -8;
horus.FORM_PREVIOUS = -9;
horus.FORM_SAVE     = -10;
horus.FORM_RESTORE  = -11;
horus.FORM_PAYMENT  = -12;

horus.HTML_FOCUS    = 0x01;
horus.HTML_SCROLL   = 0x02;
horus.HTML_WAITBOX  = 0x04;
horus.HTML_NOBACK   = 0x08;
horus.HTML_VISIBLE  = 0x10;


horus.NBSP = '\u00a0';
horus.NUL  = '\u2400';
horus.BR   = [ 'br' ];


Function.prototype.horus$standardApply=Function.prototype.apply;

Function.prototype.apply=
  function ( obj, argv ) {
    if (arguments.length==1 && horus.isArray(obj)) { argv=obj; obj=window }
    return argv ? this.horus$standardApply(obj, argv) : this.horus$standardApply(obj);
  };


// fix (IE: totally, Safari: partially) broken String split method
//
if ('ab'.split(/a(.)/).length<3) {
  String.prototype.horus$brokenSplit=String.prototype.split;

  String.prototype.split=
    function(pattern, limit) {
      var split;

      if (!(pattern instanceof RegExp && '('.test(pattern)))
	split=this.horus$brokenSplit(pattern, limit);
      else {
	var str=this;
	split=[];

	while (str.length>0) {
	  var point=str.search(pattern);
	  if (point<0) { split.push(str); break }
	  split.push(str.substring(0, point));
	  var snip=str.match(pattern);
	  str=str.substring(point+snip[0].length);
	  snip.shift();
	  while (snip.length>0) split.push(snip.shift());
	}
      }

      if (split.length) {
	if (limit && limit<split.length) split=split.slice(0, limit);

	if (typeof split[0]=='object')
	  for (var i=0; i<split.length; i++) split[i]=split[i].toString();

      }

      return split;
    };

}


String.prototype.horus$standardSplit=String.prototype.split;

String.prototype.split=
  function(pattern, limit, options) {
    if (options==undefined && typeof limit!='number') {
      options=limit;
      limit=undefined;
    }

    var opt={};

    if (options)
      switch (typeof options) {

      case 'boolean':
	opt.sensible=options;
        break;

      case 'string':
	if (options.indexOf(',')>=0)
	  options=options.horus$standardSplit(/, */);
	else
	  options=[ options ];

	for (var i=0; i<options.length; i++) {
	  var option=options[i];
	    
	  if (option.slice(0, 2)=='no')
	    opt[option.slice(-2)]=false;
	  else
	    opt[option]=true;

	}

	break;

      case 'object':
	for (var tag in options) opt[tag]=options[tag];

      }

    if (opt.trim!=null && opt.sensible==null) opt.sensible=true;
    var split;

    if (this.length==0)
      split=opt.sensible ? [] : [ '' ];
    else if (!limit || !opt.sensible && limit>0)
      split=this.horus$standardSplit(pattern, limit);
    else {
      split=this.horus$standardSplit(pattern);
      var right=limit<0;
      if (right) limit=-limit;

      if (split.length>limit)
	if (right && !opt.sensible)
	  split.splice(0, split.length-limit);
	else if (pattern instanceof RegExp) {
	  var match=pattern.exec(this);
	  var substrings=match.length;
	  var matches=(split.length-1)/substrings;

	  if (matches>limit) {
	    var point=match.index;
	    var keep=(limit-1)*substrings;
	    split.splice(right ? 0 : keep, split.length-keep);

	    if (right) {
	      for (var i=0; i<matches-limit+1; i++) {
		point+=match[0].length;
		match=pattern.exec(this.substring(point));
		point+=match.index;
	      }

	      split.unshift(this.substring(0, point));
	    } else {
	      point+=match[0].length;

	      for (var i=0; i<limit-2; i++) {
		match=pattern.exec(this.substring(point));
		point+=match.index+match[0].length;
	      }

	      split.push(this.substring(point));
	    }
	  }
	} else
	  if (right)
	    split.unshift(split.splice(0, split.length-limit+1).join(pattern));
	  else
	    split.push(split.splice(limit-1, split.length-limit+1).join(pattern));

    }

    if (opt.trim)
      if (split instanceof Array)
	for (i=0; i<split.length; i++) split[i]=split[i].replace(/^\s*(.*?)\s*$/, '$1');
      else
	split=split.replace(/^\s*(.*?)\s*$/, '$1');

    return split;
  };


String.prototype.asObject=
  function ( missing ) {
    var match=this.split('.', true);
    var value=window;
    while (match.length && value) value=value[match.shift()];
    return horus.checkNull(value, missing);
  };


String.prototype.possessive=
  function () {
    return this=='' ? '' : this+(/s$/.test(this) ? '\'' : '\'s');
  };


String.prototype.test  = function ( s ) { return String(s).indexOf(this)>=0 };
String.prototype.left  = function ( c ) { return this.slice(0, c) };
String.prototype.right = function ( c ) { return this.slice(-c) };
String.prototype.trim  = function ()    { return this.replace(/^\s*(.*?)\s*$/, '$1') };
String.prototype.ltrim = function ()    { return this.replace(/^\s*/, '') };
String.prototype.rtrim = function ()    { return this.replace(/\s*$/, '') };


Number.prototype.test  = function ( s ) { return String(this).test(s) };
Number.prototype.left  = function ( c ) { return String(this).left(c) };
Number.prototype.right = function ( c ) { return String(this).right(c) };


Number.prototype.trim=Number.prototype.ltrim=Number.prototype.rtrim=
Number.prototype.toLowerCase=Number.prototype.toUpperCase=
  function () { return String(this) };


Number.prototype.ordinal=
  function () {
    var n=this;
    if (n<0) n=-n;
    n=n%100;
    if (n>3 && n<21) return 'th';
    n=n%10;
    if (n==1) return 'st';
    if (n==2) return 'nd';
    if (n==3) return 'rd';
    return 'th';
  };


Number.prototype.toCurrency=
  function ( symbol, sep ) {
    if (symbol==null) symbol='£';
    if (sep==null) sep=',';
    var display=this.toFixed(2);
    var test=new RegExp('\\d{4}[.'+sep.left(1)+']');
    var replace=new RegExp('(\\d)(\\d{3})([.'+sep.left(1)+'])');
    while (test.test(display)) display=display.replace(replace, '$1'+sep+'$2$3');
    return symbol+display;
  };


Number.prototype.toGrouped=
  function ( sep ) {
    if (sep==null) sep=',';
    var display=this.toString();
    var test=new RegExp('\\d{4}('+sep+'|$)');
    var replace=new RegExp('(\\d)(\\d{3})('+sep+'|$)');
    while (test.test(display)) display=display.replace(replace, '$1'+sep+'$2$3');
    return display;
  };


Number.prototype.toSize=
  function ( options ) {
    options=horus.options
      (options, { units: '', decimals: 2, decimal: false, point: '.' },
       { string: 'units', numeric: 'decimals', boolean: 'decimal' });

    var space='';
    var size=this;
    var factor=options.decimal ? 1000 : 1024;
    var magnitude=-1;
    while (++magnitude<=4 && size>=factor) size/=factor;

    if (options.units!='' && options.units.left(1)==' ') {
      space=' ';
      units=units.ltrim();
    }

    if (magnitude>0) {
      switch (magnitude) {

      case 1: magnitude=options.decimal ? 'k' : 'K';	break;
      case 2: magnitude='M';				break;
      case 3: magnitude='G';				break;
      case 4: magnitude='T';				break;

      }

      if (options.point=='')
	options.point=magnitude;
      else
	options.units=magnitude+options.units;

    }

    size=size.toFixed(options.decimals);
    if (options.point!='.') size=size.replace(/\./g, options.point);
    if (options.units!='') options.units=space+options.units;
    return size+options.units;
  };


Boolean.parse=
  function ( value ) {
    if (horus.isBoolean(value)) return value;
    if (horus.isNumber(value)) return Number(value)!=0;

    if (horus.isString(value))
      return /^\s*(t(rue)?|y(es)?)\s*$/i.test(value) ? true :
	/^\s*(f(alse)?|no?)?\s*$/i.test(value) ? false : null;

    if (value==null) return false;
    if (value instanceof Array) return value.length>0;
    return null;
  };


Array.prototype.isEmpty=function () { return this.length==0 };


Array.prototype.clone=
  function () {
    var clone=new Array(this.length);
    for (var i=0; i<this.length; i++) clone[i]=this[i];
    if (arguments.length) clone.addList.apply(clone, arguments);
    return clone;
  };


Array.prototype.copy=
  function ( from ) {
    this.length=from.length;
    for (var i=0; i<from.length; i++) this[i]=from[i];
    return this;
  };


Array.prototype.find=
  function ( value, point ) {
    if (point==null) point=0;
    while (point<this.length && this[point]!=value) point++;
    return point<this.length ? point : null;
  };


Array.prototype.addList=
  function () {
    for (var i=0; i<arguments.length; i++) {
      var item=arguments[i];

      if (horus.isArray(item))
	for (var j=0; j<item.length; this.push(item[j++]));
      else
	this.push(item);

    }

    return this;
  };


Array.prototype.addString=
  function () {
    var prev=this.length && horus.isString(this[this.length-1]);

    for (var i=0; i<arguments.length; i++) {
      var obj=arguments[i];
      var next=horus.isString(obj);

      if (prev && next)
	this[this.length-1]+=obj;
      else
	this.push(obj);

      prev=next;
    }

    return this;
  };


Array.prototype.clean=
  function () {
    for (var i=this.length-1; i>=0; i--)
      if (this[i]=='' || this[i]==null)
	this.splice(i, 1);

    return this;
  };


Array.prototype.enumerate=
  function () {
    this.clean();
    for (var i=0; i<this.length; i++) this[i]=Number(this[i]);
    return this;
  };


horus.typeOf=
  function ( obj, fromstring ) {
    var t=typeof obj;

    if (t=='object' && obj!=null)
      if (obj instanceof String)
	t='string';
      else if (obj instanceof Number)
	t='number';
      else if (obj instanceof Boolean)
	t='boolean';

    if (fromstring && t=='string')
      if (/^\s*(t(rue)?|f(alse)?|y(es)?|no?)?\s*$/i.test(obj))
	t='boolean';
      else if (/^\s*[+-]?\d+\s*$/.test(obj))
	t='number';

    return t;
  };


horus.isString=
  function ( obj ) {
    return horus.typeOf(obj)=='string';
  };


horus.isBoolean=
  function ( obj, fromstring ) {
    return horus.typeOf(obj, fromstring)=='boolean';
  };


horus.isNumber=
  function ( obj ) {
    return horus.typeOf(obj, true)=='number';
  };


horus.isArray=
  function ( obj ) {
    if (obj==null || typeof obj!='object') return false;
    if (obj instanceof Array) return true;
    if (typeof obj.callee=='function' && typeof obj.length=='number') return true;
    return false;
  };


horus.isSimpleValue=
  function ( obj ) {
    return /^(string|number|boolean)$/.test(horus.typeOf(obj));
  };


horus.isNode=
  function ( obj ) {
    if (!obj) return false;

    try { return obj instanceof Node } catch (err) {
      return typeof obj=='object' && obj.nodeType;
    };
  };


horus.isElement=
  function ( obj ) {
    return horus.isNode(obj) && obj.nodeType==1
  };


horus.isWindow=
  function ( obj ) {
    if (!obj) return false;

    try { return obj instanceof Window } catch (err) {
      return typeof obj=='object' && obj.window==obj;
    };
  };


horus.hasValue=
  function ( obj ) {
    switch (horus.typeOf(obj)) {

    case 'string':    return obj!='';
    case 'number':    return obj!=0;
    case 'boolean':   return obj!=false;
    case 'function':  return true;
    case 'undefined': return false;

    case 'object':
      if (obj==null) return false;
      if (typeof obj.isEmpty=='function') return !obj.isEmpty();
      if (typeof obj.hasValue=='function') return obj.hasValue();
      return !horus.hash.empty(obj);

    }
  };


horus.className=
  function ( object, className ) {
    var name=
      typeof object.className=='function' ? object.className() :
      object.className ? object.className :
      object.constructor ? object.constructor.className : null;

    if (className==null) return name;
    if (typeof className!='string') className=horus.className(className);
    return name==className;
  };


horus.toArray=
  function ( object ) {
    if (object instanceof Array) return object;
    var result=new Array(object.length);
    for (var i=0; i<object.length; result.push(object[i++]));
    return result;
  };


horus.toString=
  function ( arg, quote, done ) {
    if (arg==null) return horus.NUL;
    if (quote && horus.isString(arg)) return '“'+arg+'”';
    if (horus.isSimpleValue(arg)) return arg;
    if (!done) done=[];
    var loop=done.find(arg);
    done.push(arg);

    if (horus.isArray(arg)) {
      if (loop) return '[ … ]';
      var text=[];
      for (var i=0; i<arg.length; i++) text.push(horus.toString(arg[i], true, done));
      return '[ '+text.join(', ')+' ]';
    }

    var tag='';

    if (horus.isElement(arg)) {
      tag=arg.tagName.toLowerCase();
      if (arg.name) tag+=':'+arg.name;
      if (arg.id) tag+='#'+arg.id;
      if (arg.className) tag+='.'+arg.className;
    } else
      tag=horus.className(arg);

    if (arg.constructor==Function)
      arg=arg.toString().replace(/\{(.+|\n+)*}/, '{ … }');
    else if (arg.toString && arg.toString!=Object.prototype.toString &&
	arg.toString!=horus.hash.prototype.toString)
      arg=arg.toString();
    else if (loop)
      arg='{ ∞ }';
    else {
      var keys=horus.keys(arg);

      for (var k=0; k<keys.length; k++) {
	var key=keys[k];
	keys[k]=key+': '+horus.toString(arg[key], true, done);
      }

      arg='{ '+keys.join(', ')+' }';
    }

    if (tag!=null && tag!='') arg='«'+tag+'»'+arg;
    return arg;
  };


horus.keys=
  function ( hash, all, sorter ) {
    var keys=[];

    for (var key in hash)
      if (all || hash.hasOwnProperty && hash.hasOwnProperty(key)) keys.push(key);

    return (arguments.length<3 ? keys.sort() : sorter ? keys.sort(sorter) : keys);
  };


horus.isEqual=
  function ( a, b ) {
    if (a==b) return true;
    if (a==null || b==null) return false;
    var aa=typeof a;
    if (aa!='object' || aa!=typeof b) return false;

    if (a instanceof Array) {
      if (!(b instanceof Array)) return false;
      if (a.length|=b.length) return false;

      for (var i=0; i<a.length; i++)
	if (!horus.isEqual(a[i], b[i])) return false;

    }

    if (!horus.isEqual(horus.keys(a), horus.keys(b))) return false;

    for (var k in a)
      if (!horus.isEqual(a[k], b[k])) return false;

    return true;
  };


horus.hash=function () { horus.hash.merge(this, arguments) };
horus.hash.className='horus.hash';


horus.hash.merge=
  function ( hash, argv, offset, delimiter ) {
    if (offset==null) offset=0;

    for (var arg=offset; arg<argv.length; arg++) {
      var from=argv[arg];

      if (from!=null)
	if (horus.isString(from)) {
	  if (!delimiter) delimiter=',';
	  from=from.toLowerCase().split(delimiter, 'trim');

	  for (var i=0; i<from.length; i++) {
	    var item=from[i];
	    if (item=='') continue;
	    var split=item.search(/ *= */);

	    if (split>=0) {
	      var val=item.substring(split+1);

	      if (val=='true' || val=='false')
		val=val=='true';
	      else if (val.match(/^[+-]?\d+$/))
		val=Number(val);

	      hash[item.substring(0, split)]=val;
	    } else
	      if (item.substring(0, 2)=='no')
		hash[item.substring(2)]=false;
	      else
		hash[item]=true;

	  }
	} else
	  for (var key in from) hash[key]=from[key];

    }

    return hash;
  };


horus.hash.key=
  function ( hash ) {
    for (var key in hash)
      if (!hash.hasOwnProperty || hash.hasOwnProperty(key)) return key;

    return undefined;
  };


horus.hash.get=
  function ( hash, key, val ) {
    if (key==undefined) key=horus.hash.key(hash);
    return key in hash ? hash[key] : val ;
  };


horus.hash.keys  = horus.keys;
horus.hash.size  = function ( hash ) { return horus.keys(hash).length };
horus.hash.empty = function ( hash ) { return horus.hash.key(hash)==null };
horus.hash.add   = function ( hash ) { return horus.hash.merge(hash, arguments, 1) };
horus.hash.copy  = function () { return horus.hash.merge({}, arguments) };


horus.hash.prototype.merge=
  function ( delimiter ) {
    return horus.hash.merge(this, arguments, 1, delimiter);
  };


horus.hash.prototype.get=
  function ( key, val ) {
    return horus.hash.get(this, key, val);
  };


horus.hash.prototype.toString = function () { return horus.toString(this, true) };
horus.hash.prototype.add      = function () { return horus.hash.merge(this, arguments) };
horus.hash.prototype.key      = function () { return horus.hash.key(this) };
horus.hash.prototype.keys     = function () { return horus.keys(this) };
horus.hash.prototype.size     = function () { return this.keys().length };
horus.hash.prototype.isEmpty  = function () { return this.key()==null };
horus.hash.prototype.isEqual  = function ( hash ) { return horus.isEqual(this, hash) };


horus.options=
  function ( optionsin, def, key, delimiter ) {
    var optionsout=new horus.hash;
    if (def) optionsout.merge(delimiter, def);

    if (key) {
      var select=horus.typeOf(optionsin);

      if (typeof key=='object' && /^(string|number|boolean|function)$/.test(select) &&
	  select in key && !(select=='string' && (optionsin=='' || /=/.test(optionsin))))
	optionsin=key[select]+'='+optionsin;
      else if (select=='boolean' && horus.isString(key))
	optionsin=key+'='+optionsin;

    }

    if (optionsin) optionsout.merge(delimiter, optionsin);
    return optionsout;
  };


horus.callable=
  function ( method ) {
    if (!method) return false;
    if (typeof method=='function') return true;
    if (!(method instanceof Array)) return false;
    if (typeof method[0]=='function') return true;
    if (typeof method[0]!='object' || method.length<2) return false;
    if (typeof method[1]=='function') return true;
    if (!horus.isString(method)) return false;
    if (typeof method[0][method[1]]=='function') return true;
    return false;
  };


horus.eventListener=
  function ( element, event, action, object ) {
    if (typeof element=='string') element=document.getElementById(element);
    if (horus.gecko && event=='mousewheel') event='DOMMouseScroll';
    var reqaction=[ action ];

    if (object) {
      var self=object;
      var method=action;
      action=function () { return method.apply(self, arguments) };
      reqaction.push(self, action);
    }

    if (!element.$events) element.$events={};
    if (!element.$events[event]) element.$events[event]=[];
    element.$events[event].push(reqaction);

    if (window.addEventListener)
      element.addEventListener(event, action, false);
    else if (window.attachEvent)
      element.attachEvent('on'+event, action);
    else {
      var old=element['on'+event];

      element['on'+event]=
	!old ? action :
	function (e) {
          if (!e) e=window.event;
	  old(e, element);
	  return action(e, element);
        };

    }
  };


horus.removeListener=
  function ( element, event, action, object ) {
    if (!element.$events) return;
    var events=element.$events[event];
    if (!events) return;

    for (var i=0; i<events.length; i++) {
      if (events[i][0]!=action) continue;

      if (object) {
	if (events[i].length<3 || events[i][1]!=object) continue;
	action=events[i][2];
      }

      if (window.removeEventListener)
	element.removeEventListener(event, action, false);
      else
	element.detachEvent('on'+event, action);

      events.splice(i, 1);
      return;
    }
  };


// mousewheel event data thanks to Adomas Paltanavičius, taken from
// http://adomas.org/javascript-mouse-wheel/, but with direction reversed (-ve is up)
//
horus.mouseDelta=
  function ( event ) {
    var delta=0;
    if (!event) event=window.event;

    if (event.wheelDelta) { // IE/Opera
      delta=event.wheelDelta/120; 
      if (!horus.opera) delta=-delta;
    } else if (event.detail) // gecko
      delta=event.detail/3;

    if (event.preventDefault) event.preventDefault();
    event.returnValue=false;
    return delta;
  };


horus.winScroll=
  function ( width, height, scroll, xpos, ypos ) {
    var options=[];

    if (width) options.push('width='+width);
    if (height) options.push('height='+height);
    if (xpos!=null) options.push((horus.ie ? 'left=' : 'screenX=')+xpos);
    if (ypos!=null) options.push((horus.ie ? 'top=' : 'screenY=')+ypos);

    if (scroll)
      if (scroll instanceof Array) {
	if (scroll[0]) options.push('scrollbars=yes');
	if (scroll[1]) options.push('resizable=yes');
      } else if (horus.isBoolean(scroll)) {
	if (Boolean.parse(scroll)) options.push('scrollbars=yes,resizable=yes');
      } else if (typeof scroll=='object') {
	for (var tag in scroll) options.push(tag+'='+scroll[tag]);
      } else
	options.push(scroll);

    return options.join(',');
  };


horus.placewin=
  function ( name, source, xpos, ypos, width, height, scroll, rethandle ) {
    if (/^opener\./.test(name)) {
      name=name.replace(/^opener\./, '');

      if (opener && opener.horus)
	return opener.horus.placewin
	  (name, source, xpos, ypos, width, height, scroll, rethandle);
	
    }

    var wh=window.open(source, name, horus.winScroll(width, height, scroll, xpos, ypos));
    horus.focus(wh);
    if (rethandle) return wh;
  };


horus.openwin=
  function ( name, source, width, height, scroll, rethandle ) {
    return horus.placewin(name, source, null, null, width, height, scroll, rethandle);
  };


horus.closewin=
  function ( dest ) {
    if (window.opener && window.opener!=window) {
      if (dest) window.opener.document.location.replace(dest);
      window.close();
    } else if (dest)
      window.document.location.replace(dest);

    return false;
  };


horus.deframe=
  function ( replace ) {
    if (window.parent==window) return;

    if (replace)
      window.top.location.replace(window.location.href);
    else
      window.top.location.href=window.location.href;

  };


horus.mailto=
  function ( name, domain, subject, body ) {
    var url='mailto:'+encodeURIComponent(name+'@'+domain);
    if (subject!='') url+='?subject='+encodeURIComponent(subject);
    if (body!='') url+=(subject=='' ? '?' : '&')+'body='+encodeURIComponent(body);
    document.location.href=url;
  };


// Image rollover - normally triggered by onmouseover/onmouseout. The
// assumption is made that the image filename is <something>.<index>.<type>
// where <index> is an integer with zero as the base state.
//
// Parameters:
//   image - Image object, or image name/index within the document
//   over  - (optional) new image index, default 0
//
horus.mouseover=
  function ( image ) {
    var over=arguments.length>1 ? arguments[1] : 0;

    if (image instanceof Array)
      for (var iptr=0; iptr<image.length; iptr++) horus.mouseover(image[iptr], over);
    else {
      if (typeof image=='string') image=document.images[image];

      if (over=='*') {
	var old=Number(image.src.match(/\.(\d+)\.\w+$/)[1]);
	over=old%2 ? old-1 : old+1;
      }

      image.src=image.src.replace(/\.\d+(\.\w+)$/, '.'+over+'$1');
    }
  };


horus.rollover=function ( image ) { horus.mouseover(image, '*') };


// return window vertical scroll position
//
horus.scrollv=
  function () {
    if (document.documentElement && document.documentElement.scrollTop)
      return document.documentElement.scrollTop;

    if (document.body && document.body.scrollTop!=null)
      return document.body.scrollTop;

    return window.pageYOffset;
  };


// return window horizontal scroll position
//
horus.scrollh=
  function () {
    if (document.documentElement && document.documentElement.scrollLeft)
      return document.documentElement.scrollLeft;

    if (document.body && document.body.scrollLeft!=null)
      return document.body.scrollLeft;

    return window.pageXOffset;
  };


// return window size
//
horus.windowSize=
  function ( win ) {
    var size={};
    if (!win) win=window;

    if (win.innerHeight) {
      size.height=win.innerHeight;
      size.width=win.innerWidth;
    } else {
      win=win.document;

      if (win.documentElement && win.documentElement.clientHeight) {
	size.height=win.documentElement.clientHeight;
	size.width=win.documentElement.clientWidth;
      } else {
	size.height=win.body.clientHeight;
	size.width=win.body.clientWidth;
      }
    }

    return size;
  };


// return window size, and top, bottom, left and right coördinates
//
horus.windowPos=
  function () {
    var winsize=horus.windowSize();
    winsize.top=horus.scrollv();
    winsize.left=horus.scrollh();
    winsize.bottom=winsize.top+winsize.height;
    winsize.right=winsize.left+winsize.width;
    return winsize;
  };


horus.event=
  function ( event, root ) {
    if (horus.isElement(event))
      event={ target: event, srcElement: event, type: 'call' };
    else if (event && typeof event.length=='number')
      event=event[0];

    if (event && event.rawevent) {
      if (event.root==root) {
	for (var k in event) this[k]=event[k];
	return this;
      }

      event=event.rawevent;
    } else if (!(event && event.type))
      event=window.event || event[0];

    this.rawevent=event;
    this.type=event.type;
    this.altKey=event.altKey;
    this.ctrlKey=event.ctrlKey;
    this.shiftKey=event.shiftKey;
    this.detail=event.detail;
    var pos=horus.windowPos();

    if (root) {
      this.root=root;
      root=horus.getPosition(root);
      pos.top-=root.top;
      pos.left-=root.left;
    }

    this.y=pos.top+event.clientY;
    this.x=pos.left+event.clientX;

    if (window.event) {
      this.target=event.srcElement;
      this.keyCode=event.keyCode;

      if (horus.ieold)
	if (event.srcElement && event.y!=event.clientY && event.x!=event.clientX &&
	    (event.y<0 || event.y>=event.srcElement.offsetHeight ||
	     event.x<0 && event.x>=event.srcElement.offsetWidth)) {
	  this.y+=10-event.y;
	  this.x+=10-event.x;
	}

    } else {
      this.target=event.target;
      this.keyCode=event.which;
    }

    return this;
  };


horus.event.className='horus.event';


horus.event.prototype.control=function () { return this.target };


horus.event.prototype.toId=
  function ( index, tag ) {
    if (!('$node' in this)) this.$node=horus.toId.$node(this.target);
    return this.$node ? horus.toId.split(this.$node, index, tag) : null;
  };


horus.event.prototype.form=
  function () {
    return horus.toForm(this);
  };


horus.event.prototype.character=
  function () {
    if (this.$character==undefined) this.$character=String.fromCharCode(this.keyCode);
    return this.$character;
  };


horus.event.prototype.preventDefault=
  function () {
    if (window.event)
      this.rawevent.returnValue=false;
    else
      this.rawevent.preventDefault();

  };


horus.event.prototype.stopPropagation=
  function () {
    if (this.rawevent.stopPropagation)
      this.rawevent.stopPropagation();
    else
      this.rawevent.cancelBubble=true;

  };


horus.mouseInBox=
  function ( event, box ) {
    if (!box) return false;
    event=new horus.event(event, box);

    return event.y>=0 && event.y<box.offsetHeight &&
           event.x>=0 && event.x<box.offsetWidth;

  };


horus.eval=
  function ( val, missing ) {
    if (horus.isString(val))
      try { val=eval(val) } catch (err) {}
    else if (horus.callable(val))
      val=horus.call(val);

    if (val==null || horus.isString(val) && val==horus.NUL)
      val=missing==undefined ? null : missing;

    return val;
  };


horus.isNull=
  function ( value, ajaxnull ) {
    return value==null || ajaxnull && horus.isString(value) && value==horus.NUL;
  };


horus.checkNull=
  function ( value, missing, ajaxnull ) {
    if (horus.isNull(value, ajaxnull)) value=missing==undefined ? null : missing;
    return value;
  };


horus.asObject=
  function ( value, missing ) {
    return horus.isString(value) ?
      value.asObject(missing) : horus.checkNull(value, missing);

  };


horus.toObject=
  function ( val ) {
    switch (typeof val) {

    case 'boolean':
      return new Boolean(val);

    case 'number':
      return new Number(val);

    case 'string':
      if (/^(true|false)$/.test(val)) return new Boolean(val=='true');
      if (/^[-+]?(\d*\.)?\d+$/.test(val)) return new Number(val);
      return new String(val);

    default:
      return val;

    }
  };


horus.toBoolean=
  function ( val ) {
    if (typeof val=='boolean') return val;
    if (typeof val=='number') return val!=0;
    if (!Boolean(val)) return false;
    if (val.trim().match(/^(0|f(alse)?|n(o)?|off)?$/i)) return false;
    return true;
  };


horus.toDate=
  function ( val, def ) {
    if (val==null) return def;
    if (val instanceof Date) return val;
    if (val=='') return def;

    var date=
      /^(?:\{ts ')?(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)(?:\.(\d+))?(?:'})?$/.exec(val) ||
      /^(\d+),(\d+),(\d+)(?:,(\d+),(\d+)(?:,(\d+)(?:,(\d+))?)?)?$/.exec(val);

    if (date) {
      for (var i=3; i<8; i++) if (!date[i]) date[i]=0;
      return new Date(date[1], date[2]-1, date[3], date[4], date[5], date[6], date[7]);
    }

    return new Date(val);
  };


horus.toForm=
  function ( theForm, theField ) {
    if (theForm==null || typeof theForm=='boolean')
      if (theField && theField.form)
	theForm=theField.form;
      else
	for (var i=0; i<document.forms.length; i++) {
	  var thisForm=document.forms[i];

	  if (theForm==null || !/^(statusbox|popover)form/.test(thisForm.name)) {
	    theForm=thisForm;
	    if (!/(^(statusbox|popover)form|search)/.test(theForm.name)) break;
	  }
	}

    if (typeof theForm=='string' || typeof theForm=='number')
      theForm=document.forms[theForm];
    else if (theForm) {
      if (theForm instanceof horus.event) theForm=theForm.target;

      if (theForm.form)
	theForm=theForm.form;
      else if (!theForm.elements)
	theForm=horus.parentTag(theForm, 'form'); // needs dom.js!

    }

    return theForm;
  };


// nasty... have to do this because the argument "array" of a JavaScript
// function isn't a real Array, so we can't splice or shift it
//
horus.argv=
  function ( argv, odd ) {
    var argc=argv.length;
    var splice=odd==null || argc%2==(odd ? 0 : 1);
    var newargs=new Array(splice ? argc : argc+1);
    var outptr=0;
    if (!splice) newargs[outptr++]=null;
    for (inptr=0; inptr<argc; inptr++) newargs[outptr++]=argv[inptr];
    return newargs;
  };


horus.submitArgv=
  function( argv ) {
    argv=horus.argv(argv, false);
    var theForm=argv[0];
    var validate=false;

    if (theForm!=null)
      if (theForm instanceof Array) {
	validate=theForm[1];
	theForm=theForm[0];
      } else if (typeof theForm=='function') {
	validate=theForm;
	theForm=null;
      }

    argv[0]=[ horus.toForm(theForm), validate ];
    return argv;
  };


horus.formArgv=
  function ( argv, odd ) {
    argv=horus.argv(argv, odd);
    argv[0]=horus.toForm(argv[0], argv[1]);
    return argv;
  };


horus.isMulti=
  function ( elem ) {
    if (elem instanceof Array) elem=elem[0];
    if (elem && !elem.tagName && 'length' in elem) return true;
    if (elem.tagName.toLowerCase()!='input') return false;
    return elem.type=='checkbox' || elem.type=='radio';
  };


horus.formField=
  function () {
    var theForm, theField, multi;

    if (arguments[0] instanceof Array) {
      theForm=arguments[0][0];
      theField=arguments[0][1];
      multi=arguments[1];
    } else if (arguments.length>1) {
      theForm=arguments[0];
      theField=arguments[1];
      multi=arguments[2];
    } else
      theField=arguments[0];

    theForm=horus.toForm(theForm, theField);
    var isarray;

    if (typeof theField=='string' && theField.charAt(0)!='#') {
      var theName=theField;

      if (horus.brokenDOM) {
	// it beggars belief, really
	theField=[];
	isarray=true;

	for (var i=0; i<theForm.elements.length; i++)
	  if (theForm.elements[i].name==theName) theField.push(theForm.elements[i]);

	if (theField.length==0) theField=null;
      } else
	theField=theForm.elements[theName];

    } else if (typeof theField=='string')
      theField=document.getElementById(theField.substring(1));

    if (theField) {
      var theTag=theField.tagName || theField[0].tagName;

      if (theTag.toLowerCase()=='input') {
	if (multi==null) multi=horus.isMulti(theField);

	if (multi && !theField.length) {
	  theField=theForm.elements[theField.name];
	  if (!theField.length) theField=[ theField ];
	} else if (!multi && theField.length)
	  theField=theField[0];

      } else if (isarray)
	theField=theField[0];

    }

    return theField;
  };


horus.set=
  function ( theForm, argname, argvalue ) {
    var arg=horus.formField(theForm, argname);
    if (arg) arg.value=argvalue;
  };


horus.toId=
  function ( node, index, tag ) {
    if (!horus.isNumber(node)) {
      node=horus.toId.$node(node);
      if (node) node=horus.toId.split(node, index, tag);
    }

    return node;
  };


horus.toId.base=
  function ( node, index ) {
    node=horus.toId.$node(node);
    if (node) node=horus.toId.split(node, index, true)[0];
    return node;
  };


horus.toId.split=
  function ( node, index, tag ) {
    if (typeof index=='boolean') {
      tag=index;
      index=null;
    }

    if (index==null)
      index=/^(.*?)(\d*)$/.exec(node);
    else if (index==0)
      index=/^.*?(\D+)(\d*)$/.exec(node);
    else if (index==1)
      index=/^.*?(\D+)(\d+)\D+\d*$/.exec(node);
    else {
      if (!horus.toId.$match) horus.toId.$match=[];

      if (!horus.toId.$match[--index])
	horus.toId.$match[index]=
	  new RegExp('^.*?(\\D+)(\\d+)(\\D+\\d+){'+index+'}\\D+\\d*$');

      index=horus.toId.$match[index].exec(node);
    }

    if (index) {
      node=index[2];
      if (/^\d+$/.test(node)) node=Number(node);
      if (tag) node=[ index[1], node ];
    } else
      node=null;

    return node;
  };


horus.toId.$node=
  function ( node ) {
    if (node!=null) {
      node=horus.getElement(node);

      while (node) {
	if (node.id!=null && node.id!='') { node=node.id; break }
	if (node.name!=null && node.name!='') { node=node.name; break }
	node=node.parentNode;
      }
    }

    return node;
  };


horus.toNode=
  function ( prefix, id ) {
    if (id==null || id=='') return horus.getElement(prefix);
    if (horus.isNode(prefix)) prefix=prefix.id;
    return document.getElementById(prefix.replace(/\d*$/, horus.toId(id)));
  };


horus.getElement=
  function ( item, noevent ) {
    if (!(horus.isNode(item) || horus.isWindow(item)))
      if (horus.isString(item))
	item=document.getElementById(item.left(1)=='#' ? item.right(-1) : item);
      else if (item && typeof item.control=='function')
	item=item.control();
      else if (!noevent)
	try { item=new horus.event(item).control() } catch (err) {};

    return item;
  };


horus.$makeLink=
  function ( argv ) {
    var url=argv[0];
    var offset=1;

    if (!horus.isString(url)) {
      if (url) offset=0;
      url='';
    }

    if (!/^(https?|ftp|mailto):/.test(url)) {
      if (url=='')
	url=document.location.pathname;
      else {
	var leadin=url.left(1);

	if (leadin=='?')
	  url=document.location.pathname+url;
	else if (leadin!='/')
	  url=document.location.pathname.replace(/\/[^\/]*$/, '/')+url;

      }

      url=document.location.protocol+'//'+document.location.host+url;
    }

    var params=[];

    for (var ptr=offset; ptr<argv.length; ptr++) {
      var arg=argv[ptr];

      if (arg!=null)
	if (horus.isString(arg)) {
	  var point=arg.indexOf('=');

	  if (point<0)
	    params.push(encodeURIComponent(arg));
	  else
	    params.push
	      (encodeURIComponent(arg.left(point))+'='+
	       encodeURIComponent(arg.right(-point-1)));

	} else
	  for (tag in arg) {
	    var value=arg[tag];
	    tag=encodeURIComponent(tag);

	    if (value instanceof Array)
	      for (var i=0; i<value.length; i++)
		params.push(tag+'='+encodeURIComponent(value[i]));

	    else
	      params.push(tag+'='+encodeURIComponent(value));

	  }

    }

    if (params.length) url+=('?'.test(url) ? '&' : '?')+params.join('&');
    return url;
  };


horus.makeLink = function ( url ) { return horus.$makeLink(arguments) };
horus.linkTo   = function ( url ) { document.location.href=horus.$makeLink(arguments) };
horus.noBack   = function ()      { if (window.history) window.history.forward() };
horus.goCheck  = function ()      { if (horus.$goCheck) horus.$goCheck.submit() };


// generic form submit
//
horus.doit=
  function ( submitv, argv, noscroll ) {
    argv=horus.submitArgv(argv);
    var parms=argv.shift();
    var theForm=parms[0];
    var validate=parms[1];
    var argc=argv.length;
    var scrollv=noscroll ? 'no' : 0;

    if (submitv==horus.FORM_RESET)
      theForm.reset();
    else if (theForm.onsubmit) {
      status=theForm.onsubmit();
      if (status==false) return false;
    }

    for (var i=0; i<argc; i+=2) {
      var argname=argv[i];
      var argvalue=argv[i+1];

      if (argname=='_scroll')
	scrollv=argvalue;
      else if (argname=='_submit')
	submitv=argvalue;
      else
	horus.set(theForm, argname, argvalue);

    }

    if (horus.script.isLoaded('visible'))
      horus.set(theForm, '_visible', horus.visible.get());

    horus.set(theForm, '_scroll', scrollv=='no' ? 0 : horus.scrollv()+scrollv);
    horus.set(theForm, '_submit', submitv);

    if (validate) {
      var status=validate(theForm);

      if (typeof status=='string')
	if (status!='') {
	  alert(status);
	  status=false;
	} else
	  status=true;

      if (!status) return false;
    }

    if (!theForm.target || theForm.target=='_self')
      for (var ptr=0; ptr<theForm.length; ptr++) {
	var element=theForm.elements[ptr];
	if (element.type=='button' || element.type=='submit') element.disabled=true;
      }

    if (horus.$waitBox && horus.script.isLoaded('popup')) horus.waitBox(true);
    theForm.submit();
    if (horus.iewin || horus.opera) horus.$goCheck=theForm; // IE/Win and Opera
  };


// form submit functions - the submit values are defined in Horus::HTML for Perl
// and cf_submit for ColdFusion
//
horus.reentry     = function () { return horus.doit(horus.FORM_ENTRY,    arguments) };
horus.go          = function () { return horus.doit(horus.FORM_SUBMIT,   arguments) };
horus.reload      = function () { return horus.doit(horus.FORM_REFRESH,  arguments) };
horus.nogo        = function () { return horus.doit(horus.FORM_RESET,    arguments) };
horus.do_new      = function () { return horus.doit(horus.FORM_NEW,      arguments) };
horus.do_add      = function () { return horus.doit(horus.FORM_ADD,      arguments) };
horus.do_delete   = function () { return horus.doit(horus.FORM_DELETE,   arguments) };
horus.do_edit     = function () { return horus.doit(horus.FORM_EDIT,     arguments) };
horus.do_next     = function () { return horus.doit(horus.FORM_NEXT,     arguments) };
horus.do_previous = function () { return horus.doit(horus.FORM_PREVIOUS, arguments) };
horus.do_save     = function () { return horus.doit(horus.FORM_SAVE,     arguments) };
horus.do_restore  = function () { return horus.doit(horus.FORM_RESTORE,  arguments) };
horus.do_payment  = function () { return horus.doit(horus.FORM_PAYMENT,  arguments) };


horus.scroller=
  function () {
    if (document.URL.match(/#/)) return;
    var theForm=horus.formArgv(arguments, false)[0];
    if (theForm._scroll) window.scrollTo(0, parseInt(theForm._scroll.value));
  };


horus.focus=
  function () {
    for (var i=0; i<arguments.length; i++) {
      var node=horus.getElement(arguments[i]);
      if (node)	try { node.focus(); return } catch ( err ) {};
    }
  };


horus.$wrap=
  function ( argv, action ) {
    argv=horus.formArgv(argv, true);
    var theForm=argv[0];
    var theName=argv[1];
    if (typeof theName!='string') theName=theName.name;
    if (theForm.$wrap==null) theForm.$wrap={};
    if (theForm.$wrap[theName]==null) theForm.$wrap[theName]=0;

    if (action>0)
      theForm.$wrap[theName]++;
    else if (action<0 && theForm.$wrap[theName]>0)
      theForm.$wrap[theName]--;

    return theForm.$wrap[theName];
  };

horus.wrap    = function () { return horus.$wrap(arguments, 1) };
horus.unwrap  = function () { return horus.$wrap(arguments, -1) };
horus.wrapped = function () { return horus.$wrap(arguments, 0) };


horus.setvar=
  function () {
    var argv=horus.formArgv(arguments, false);
    var elem=horus.formField(argv, false);
    var form=argv[0];
    var value=argv[2];

    if (elem.tagName.toLowerCase()=='select')
      horus.setselected(form, elem, value);
    else if (elem.type=='radio')
      horus.setradio(form, elem, value);
    else if (elem.type=='checkbox')
      horus.setchecked(form, elem, value);
    else
      elem.value=value==null ? '' : value;

  };


horus.getvar=
  function () {
    var argv=horus.formArgv(arguments, true);
    var elem=horus.formField(argv, false);
    if (!elem) return null;
    var form=argv[0];
    if (elem.tagName.toLowerCase()=='select') return horus.getselected(form, elem);
    if (elem.type=='radio') return horus.getradio(form, elem);
    if (elem.type=='checkbox') return horus.getchecked(form, elem);
    return horus.formField(argv).value;
  };


horus.form2hash=
  function ( form, exclude ) {
    form=horus.toForm(form);

    var controls=
      horus.getTags.find(form, 'input:type!button!submit!image,textarea,select');

    var data={};
    exclude=new horus.hash(exclude);

    while (controls.length) {
      var control=controls.shift();
      var name=control.name;

      if (name=='' || /___Config$/.test(name) || name in data || name in exclude)
	continue;

      data[name]=document.getElementById(name+'___Config') ?
	FCKeditorAPI.GetInstance(name).GetData() :
	horus.getvar(form, name);

    }

    return data;
  }


horus.getvaropt=
  function () {
    var argv=horus.formArgv(arguments, false);
    var elem=horus.formField(argv, false);
    if (!elem) return null;
    var thevalue=elem.value;
    var options=horus.options(argv[2]);
    if (options.trim) thevalue=thevalue.trim();
    if (options.encode) thevalue=encodeURIComponent(thevalue);
    return thevalue;
  };


horus.getradio=
  function () {
    var argv=horus.formArgv(arguments, true);
    var theRadio=horus.formField(argv, true);
    if (!theRadio) return null;

    for (var i=0; i<theRadio.length; i++)
      if (theRadio[i].checked) return theRadio[i].value;

    return null;
  };


horus.setradio=
  function () {
    var argv=horus.formArgv(arguments, false);
    var theRadio=horus.formField(argv, true);
    var value=argv[2];

    if (value==null)
      for (var i=0; i<theRadio.length; i++)
	if (theRadio[i].checked) {
	  theRadio[i].checked=false;
	  return;
	}

    for (var i=0; i<theRadio.length; i++)
      if (theRadio[i].value==value) {
	theRadio[i].checked=true;
	return;
      }

  };


horus.getchecked=
  function () {
    var argv=horus.formArgv(arguments, true);
    var theBoxes=horus.formField(argv, true);
    if (!theBoxes) return null;
    var checked=[];
    checked.boxes={};

    for (var i=0; i<theBoxes.length; i++) {
      var box=theBoxes[i];
      checked.boxes[box.value]=box.checked;
      if (box.checked) checked.push(box.value);
    }

    return checked;
  };


horus.setchecked=
  function () {
    var argv=horus.formArgv(arguments, false);
    var theBoxes=horus.formField(argv, true);
    var value=argv[2];
    var i;

    if (value!=null)
      if (typeof value=='object') {
	if (value instanceof Array) {
	  var list=value;
	  value={};
	  for (i=0; i<list.length; value[list[i++]]=true);
	}

	for (i=0; i<theBoxes.length; i++)
	  theBoxes[i].checked=horus.hash.get(value, theBoxes[i].value);

      } else if (typeof value=='boolean') {
	for (i=0; i<theBoxes.length; i++) theBoxes[i].checked=value;
      } else {
	for (i=0; i<theBoxes.length; i++)
	  if (theBoxes[i].value==value) theBoxes[i].checked=!theBoxes[i].checked;

      }

  };


horus.clearchecked=
  function () {
    var argv=horus.formArgv(arguments, true);
    argv.push(true);
    var theBoxes=horus.formField(argv, true);
    for (var i=0; i<theBoxes.length; i++) theBoxes[i].checked=false;
  };


horus.brokenselect=
  function ( theSelect ) {
    if (!horus.iewin) return 'value';
    var theOptions=theSelect.options;

    for (var i=0; i<theOptions.length; i++)
      if (theOptions[i].value!='') return 'value';

    return 'text';
  };


horus.getselected=
  function ( theForm, theSelect ) {
    var argv=horus.formArgv(arguments, true);
    theSelect=horus.formField(argv);
    var field=horus.brokenselect(theSelect);
    var theOptions=theSelect.options;

    if (theSelect.type=='select-one') {
      var selectedIndex=theSelect.selectedIndex;
      return selectedIndex>=0 ? theOptions[selectedIndex][field] : null;
    }

    var selected=[];

    for (var i=0; i<theOptions.length; i++)
      if (theOptions[i].selected) selected.push(theOptions[i][field]);

    return selected;
  };


horus.$setselected=
  function ( argv ) {
    var theForm=argv[0];
    var theSelect=argv[1];
    if (horus.wrapped(theForm, theSelect)) return;
    var theSelect=horus.formField(argv);
    var theValue=argv[2];
    var oldOption=theSelect.selectedIndex;
    var newOption=-1;

    if (theValue!=null) {
      var field=horus.brokenselect(theSelect);
      var theOptions=theSelect.options;

      for (var i=0; i<theOptions.length; i++) {
	if (theOptions[i][field]==theValue) {
	  newOption=i;
	  break;
	}
      }
    }

    if (newOption!=oldOption) {
      theSelect.selectedIndex=newOption;

      if (theSelect.onchange) {
	horus.wrap(theForm, theSelect);
	theSelect.onchange();
	horus.unwrap(theForm, theSelect);
      }
    }

    return newOption;
  };


horus.setselected=
  function () {
    var argv=horus.formArgv(arguments, false);
    for (var argp=2; argp<argv.length-1 && !argv[argp++]; ++argp);
    argv[2]=argv[argp];
    return horus.$setselected(argv);
  };


horus.setselectedif=
  function () {
    var argv=horus.formArgv(arguments, true);

    if (argv[2]) {
      argv[2]=argv[3];
      return horus.$setselected(argv);
    }
  };


horus.selectall=
  function ( theForm, theSelect ) {
    var argv=horus.formArgv(arguments, true);
    theSelect=horus.formField(argv);
    var theOptions=theSelect.options;
    for (var i=0; i<theOptions.length; theOptions[i++].selected=true);
  };


horus.compare=
  function ( a, b ) {
    if (a==null) return b==null ? 0 : -1;
    return b==null || a<b ? 1 : a>b ? -1 : 0;
  };


horus.comparenocase=
  function ( a, b ) {
    if (a==null) return b==null ? 0 : -1;
    if (b==null) return 1;
    a=a.toLowerCase();
    b=b.toLowerCase();
    return a>b ? -1 : a<b ? 1 : 0;
  };


horus.seladd=
  function ( theForm, theSelect, theValue, options, sorter ) {
    var argv=horus.formArgv(arguments, false);
    theSelect=horus.formField(argv);
    theValue=argv[2];
    options=horus.options(argv[3]);
    sorter=argv[4];
    var set=options.select || options.replace;

    if (options.clear)
      theSelect.options.length=0;
    else if (options.select && theSelect.type=='select-one' || options.replace)
      theSelect.selectedIndex=-1;

    if (typeof sorter!='function')
      sorter=sorter ? horus.compare : horus.comparenocase;

    if (theValue instanceof Array)
      for (var i=0; i<theValue.length; i++) {
	var newopt=theValue[i];
	if (typeof newopt!='object') newopt=[ newopt ];
	horus.seladd.insert(theSelect, newopt, set, sorter);
      }
    else if (horus.isSimpleValue(theValue))
      horus.seladd.insert(theSelect, theValue, set, sorter);
    else if (theValue!=null)
      for (var tag in theValue)
	horus.seladd.insert(theSelect, [ theValue[tag], tag ], set, sorter);

    return theValue;
  };


horus.seladd.insert=
  function ( select, newopt, set, oldopt ) {
    var value, text;
    if (typeof newopt=='string') newopt=newopt.split(':', 2);

    if (newopt instanceof Array) {
      text=newopt.length<1 ? '' : newopt[0];
      value=newopt.length<2 ? text : newopt[1];
      newopt=new Option(text, value, false, false);
    } else {
      text=newopt.text;
      value=newopt.value;
    }

    select=horus.getElement(select);
    var options=select.options;

    if (typeof oldopt=='function') {
      var sorter=oldopt;
      oldopt=undefined;

      for (var ptr=0; ptr<options.length; ptr++) {
	var opt=options[ptr];

	if (sorter(text, opt.text, value, opt.value)>0) {
	  oldopt=horus.brokenDOM ? ptr : opt;
	  break;
	}
      }
    } else
      if (oldopt==null)
	oldopt=undefined;
      else if (typeof oldopt=='number' && !horus.brokenDOM)
	oldopt=options[oldopt];
      else if (horus.brokenDOM && typeof oldopt!='number') {
	for (ptr=0; ptr<options.length; ptr++)
	  if (options[ptr]==oldopt) {
	    oldopt=ptr;
	    break;
	  }

	if (typeof oldopt!='number') oldopt=undefined;
      }

    if (horus.brokenDOM && oldopt==null)
      newopt=options[options.length]=new Option(newopt.text, newopt.value, false, false);
    else
      select.add(newopt, oldopt);

    newopt.selected=set;
  };


horus.selshift=
  function () {
    var argv=horus.formArgv(arguments, true);
    var theForm=argv[0];
    var source=horus.formField(theForm, argv[1]);
    var dest=argv[1];
    var sorter=argv[2];
    var items, lookup;

    if (sorter && typeof sorter=='object') {
      items=sorter.items;
      sorter=sorter.sorter;
    }

    if (items) {
      lookup={};

      for (var i=0; i<items.length; i++) {
	var o=items[i];
	if (o && 'value' in o) o=o.value;
	lookup[o]=true;
      }
    } else if (source.selectedIndex<0)
      return;

    dest=horus.formField(theForm, argv[2]);
    sorter=argv[3];
    var sourceopt=source.options;
    var sourceptr=sourceopt.length;
    var shifter=[];

    while (--sourceptr>=0) {
      var s=sourceopt[sourceptr];

      if (lookup ? lookup[s.value] : s.selected) {
	shifter.push(s);
	s.selected=true;
	sourceopt[sourceptr]=null;
      }
    }

    return horus.seladd(theForm, dest, shifter, 'replace', sorter);
  };


horus.urlparam=
  function ( name, val ) {
    var re=new RegExp('[\?&]' + name + '=([^&]+)', 'i');
    var found=re.exec(window.top.location.search);

    if (!found) {
      re=new RegExp('.*\.cfm.*/' + name + '[.=/]([^?/]+)', 'i');
      found=re.exec(window.top.location.pathname);
    }

    if (found) val=found[1];
    return val;
  };


horus.positioned=
  function ( obj ) {
    if (obj.$positioned==null)
      if (window.getComputedStyle)
	obj.$positioned=getComputedStyle(obj, '').position!='static';
      else {
	for (var child=obj.firstChild;
	     child && !child.offsetParent;
	     child=child.nextSibling);

	obj.$positioned=child && child.offsetParent==obj;
      }

    return obj.$positioned;
  };


horus.offsetParent=
  function ( obj ) {
    var parent=obj.offsetParent;

    if (horus.brokenDOM) {
      if (parent && parent.nodeName=='HTML') parent=document.body;

      while (parent && parent!=document.body && !horus.positioned(parent))
	parent=parent.parentNode;

    }

    return parent;
  };


horus.inside=
  function ( obj, container ) {
    if (!container) return false;

    while (obj) {
      if (obj==container) return true;
      obj=obj.parentNode;
    }

    return false;
  };


horus.getPosition=
  function ( obj, root, debug ) {
    obj=horus.getElement(obj);
    if (!obj) return null;
    var pos={ height: obj.offsetHeight, width: obj.offsetWidth };

    if (typeof root=='boolean')
      root=root ? null : horus.offsetParent(obj);
    else
      root=root ? horus.getElement(root) : null;

    if (debug)
      var status=[ obj.id+' - '+(root ? root.id : '*') ];

    var ptr=100;
    var lastref;
    pos.top=0;
    pos.left=0;

    while (obj && --ptr>0 && !horus.getPosition.isroot(root, obj, lastref)) {
      var offsetParent=horus.offsetParent(obj);

      if (!(offsetParent && horus.inside(offsetParent, lastref))) {
	if (debug)
	  status.push
	    (obj.nodeName+'.'+obj.id+': '+obj.offsetTop+', '+obj.offsetLeft+' - '+
	     (offsetParent ? offsetParent.nodeName+'.'+offsetParent.id : horus.NUL));

	if (obj.offsetTop) pos.top+=obj.offsetTop;
	if (obj.offsetLeft) pos.left+=obj.offsetLeft;
	if (horus.brokenDOM && !offsetParent) break;
	lastref=offsetParent;
      }

      obj=obj.parentNode;
    }

    if (debug) {
      status.push('= '+pos.top+', '+pos.left);
      alert(status.join('\n'));
    }

    pos.bottom=pos.top+pos.height-1;
    pos.right=pos.left+pos.width-1;
    return pos;
  };


horus.getPosition.isroot=
  function ( root ) {
    for (var i=1; i<arguments.length; i++) {
      var node=arguments[i];
      if (node && (node==root || node.nodeName=='BODY')) return true;
    }

    return false;
  };


horus.getHeight=
  function () {
    var theheight=0;

    var items=arguments.length==1 && arguments[0] instanceof Array
      ? arguments[0] : arguments

    for (var i=0; i<items.length; i++) {
      var item=items[i];
      var add;
      var sub;
      var thisheight;

      if (item instanceof Array) {
	add=item[1];
	sub=item[2];
	item=item[0];
      }

      if (!item) continue;

      if (item=='*')
	thisheight=horus.windowSize().height;
      else {
	item=horus.getElement(item);
	thisheight=item.offsetHeight;
      }

      if (sub) {
	if (typeof sub!='number')
	  if (typeof add!='number')
	    sub=horus.getPosition(add, sub).top;
	  else
	    sub=horus.getPosition(sub, item).top;

	thisheight-=sub;
      }

      if (add) {
	if (typeof add!='number') add=horus.getPosition(item, add).top;
	thisheight+=add;
      }

      if (thisheight>theheight) theheight=thisheight;
    }

    return theheight;
  };


horus.afterLoad=
  function () {
    if (horus.$pageLoaded)
      horus.execList(arguments);
    else {
      if (!horus.loadaction) horus.loadaction=[];
      for (var i=0; i<arguments.length; i++) horus.loadaction.push(arguments[i]);
    }
  };


horus.resizeAction=
  function () {
    if (!horus.resizeActions) horus.resizeActions=[];

    if (arguments.length==1)
      horus.resizeActions.push(arguments[0]);
    else {
      var argv=[];
      for (var i=0; i<arguments.length; i++) argv.push(arguments[i]);
      horus.resizeActions.push(argv);
    }
  };


horus.execList=
  function ( thelist ) {
    if (thelist)
      for (var i=0; i<thelist.length; i++) {
	var theaction=thelist[i];

	if (typeof theaction=='function')
	  theaction();
	else if (theaction instanceof Array) {
	  var offset=0;
	  var argv=[];

	  if (typeof theaction[0]=='number') {
	    var thedelay=theaction[offset++];
	    if (typeof theaction[offset]=='object') argv.push(theaction[offset++]);
	    argv.push(theaction[offset++], thedelay);
	    while (offset<theaction.length) argv.push(theaction[offset++]);
	    horus.setTimeout.apply(window, argv);
	  } else {
	    var object=typeof theaction=='function' ? window : theaction[offset++];
	    var fn=theaction[offset++];
	    if (typeof fn!='function') fn=object[fn];
	    while (offset<theaction.length) argv.push(theaction[offset++]);
	    fn.apply(object, argv);
	  }
	} else
	  eval(theaction);

      }

  };


horus.reallyDoResize=
  function () {
    horus.resizePending=null;

    // check if the size has *really* changed to catch spurious IE7 events
    var size=horus.windowSize();
    if (window.$resizeHeight==size.height && window.$resizeWidth==size.width) return;
    window.$resizeHeight=size.height;
    window.$resizeWidth=size.width;

    horus.execList(horus.resizeActions);
  };


horus.doResize=
  function () {
    if (horus.resizeActions && !horus.resizePending)
      horus.resizePending=setTimeout(horus.reallyDoResize, 500);

  };


horus.sizeValue=
  function ( v, units ) {
    if (!horus.isNumber(v)) return v;
    if (!units) units='px';
    return v+units;
  };


horus.analytics=
  function ( id ) {
    if (!window._gaq) {
      window._gaq=[];
      var prefix=document.location.protocol=='https:' ? 'https://ssl' : 'http://www';
      horus.script.load(prefix+'.google-analytics.com/ga.js');
    }

    _gaq.push([ horus.sitetag+'._setAccount', id ], [ horus.sitetag+'._trackPageview' ]);
  };


horus.$optset=0;
horus.$optreset=0;


horus.pageOptions=
  function ( opt ) {
    if (arguments.length==1 || arguments[1])
      horus.$optset|=opt;
    else
      horus.$optreset|=opt;

  };


horus.pageLoaded=
  function ( opt ) {
    if (!document.body) document.body=document.getElementsByTagName('body')[0];
    opt=((opt || 0)|horus.$optset)&~horus.$optreset;
    if (opt&horus.HTML_VISIBLE) horus.visible.put();
    if (opt&horus.HTML_NOBACK) horus.noBack();
    if (opt&horus.HTML_WAITBOX) horus.$waitBox=true;
    if (opt&horus.HTML_SCROLL) horus.scroller();
    if (opt&horus.HTML_FOCUS) horus.focus(window);
    horus.$pageLoaded=true;
    horus.execList(horus.loadaction);
    horus.$popflag=true;
  };


horus.eventListener(window, 'load', horus.pageLoaded);
horus.eventListener(window, 'resize', horus.doResize);


// deprecated interfaces
horus.classbyid = function ( id, newclass ) { horus.getElement(id).className=newclass };
horus.setbyid   = function ( id, content )  { horus.getElement(id).innerHTML=content };
