/**********************************************************************
 * Copyright (c) 2003-2005 Express Design Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted subject to the following conditions:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials provided
 *    with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL EXPRESS DESIGN INC. BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 **********************************************************************/

/**********************************************************************
 * Returns true if navigator.userAgent matches the given expression.
 **********************************************************************/
function uaMatch(re) {
  if (!navigator || !navigator.userAgent)
    return false;
  if (typeof(RegExp) != "function")
    return false;
  return re.exec(navigator.userAgent);
}

/**********************************************************************
 * Returns true if browser is known to be supported.  False does not
 * necessarily mean a particular program won't work.
 **********************************************************************/
var isNS      = false;
var isIE      = false;
var uaName    = null;
var uaVersion = 0;

function uaCheck()
{
  var ieCheck = uaMatch(/MSIE (\d+)/);
  if (ieCheck) {
    isIE      = true;
    uaName    = "MSIE";
    uaVersion = ieCheck[1];
    if (uaVersion >= 6)
      return true;  // MSIE, 6 or better
  }

  var geckoCheck = uaMatch(/Gecko\/(\d{6})/);
  if (geckoCheck) {
    isNS      = true;
    uaName    = "Gecko";
    uaVersion = geckoCheck[1];
    if (uaVersion >= 200208)
      return true;  // compatible build of Gecko
  }

  var safariCheck = uaMatch(/Safari\/(\d+)/);
  if (safariCheck) {
    isNS      = true;
    uaName    = "Safari";
    uaVersion = safariCheck[1];
    if (uaVersion >= 125)
      return true;  // Safari, v125 or better
  }

  return false;
}

/**********************************************************************
 * This variable is true if the browser is known to support all functions
 * of this library.  The browsers checked by uaCheck() should cover the
 * overwhelming majority of current browsers.
 **********************************************************************/
var uaOK = uaCheck();

/**********************************************************************
 * Object definition testing
 **********************************************************************/
function defined(obj) {
  return typeof(obj) != "undefined";
}

function notdefined(obj) {
  return typeof(obj) == "undefined";
}

/**********************************************************************
 * RGBColor - a color represented by RGB components
 **********************************************************************/
function RGBColor(red, green, blue)
{
  this.r = Math.round(red);
  this.g = Math.round(green);
  this.b = Math.round(blue);
  if (this.r < 0)   this.r = 0;
  if (this.r > 255) this.r = 255;
  if (this.g < 0)   this.g = 0;
  if (this.g > 255) this.g = 255;
  if (this.b < 0)   this.b = 0;
  if (this.b > 255) this.b = 255;

  this.getR = function() { return this.r; }
  this.getG = function() { return this.g; }
  this.getB = function() { return this.b; }

  this.toString = function() {
    var ret = "#";
    if (this.r < 16) ret += "0";
    ret += this.r.toString(16);
    if (this.g < 16) ret += "0";
    ret += this.g.toString(16);
    if (this.b < 16) ret += "0";
    ret += this.b.toString(16);
    return ret;
  }

  this.toRGBtring = function() {
    return "rgb("+ this.r + "," + this.g + "," + this.b + ")";
  }

  this.adjust = function(d1, d2, d3) {  // one arg or three, returns new obj
    var d1n = parseFloat(d1);
    if (d2 != null)
      var d2n = parseFloat(d2);
    if (d3 != null)
      var d3n = parseFloat(d3);

    if ((typeof(d1)=="string") && (d1.lastIndexOf("%")==(d1.length-1)))
      var d1pct = true;
    if ((typeof(d2)=="string") && (d2.lastIndexOf("%")==(d2.length-1)))
      var d2pct = true;
    if ((typeof(d3)=="string") && (d3.lastIndexOf("%")==(d3.length-1)))
      var d3pct = true;

    var newRed, newGreen, newBlue;

    if (d1pct)
      newRed = this.r + (this.r / 100 * d1n);
    else
      newRed = this.r + d1n;

    if (d2 == null) {  // implies d3 == null
      if (d1pct) {
        newGreen = this.g + (this.g / 100 * d1n);
        newBlue  = this.b + (this.b / 100 * d1n);
      } else {
        newGreen = this.g + d1n;
        newBlue  = this.b + d1n;
      }
    } else {    // both d2 and d3 are supplied
      if (d2pct)
        newGreen = this.g + (this.g / 100 * d2n);
      else
        newGreen = this.g + d2n;

      if (d3pct)
        newBlue = this.b + (this.b / 100 * d3n);
      else
        newBlue = this.b + d3n;
    }

    return new RGBColor(newRed, newGreen, newBlue);
  }

  this.adjustBy = function(d1, d2, d3) {   // adjusts this instance in-place
    var newColor = this.adjust(d1, d2, d3);
    this.r = newColor.r;
    this.g = newColor.g;
    this.b = newColor.b;
  }
}

RGBColor.fromHex = function(hexStr) {   // "[#]rrggbb"
  if (typeof(hexStr) != "string") 
    return null;
  if (hexStr.charAt(0) == "#")
    hexStr = hexStr.slice(1);
  var rstr = hexStr.slice(0, 2);
  var gstr = hexStr.slice(2, 4);
  var bstr = hexStr.slice(4, 6);
  return new RGBColor(parseInt(rstr, 16),
                      parseInt(gstr, 16),
                      parseInt(bstr, 16));
}

RGBColor.fromRGB = function(rgbStr) {   // "rgb(r,g,b)"
  if (typeof(rgbStr) != "string") 
    return null;
  var parse = /rgb\((\d+), ?(\d+), ?(\d+)\)/.exec(rgbStr);
  if (!parse)
    return null;
  else
    return new RGBColor(parse[1], parse[2], parse[3]);
}

RGBColor.fromString = function(str) {
  if (typeof(str) != "string")
    return null;
  if (str.charAt(0) == "#")
    return RGBColor.fromHex(str);
  else if (str.substr(0,3) == "rgb")
    return RGBColor.fromRGB(str);
  else
    return null;
}

var rgbWhite = new RGBColor(255, 255, 255);
var rgbBlack = new RGBColor(  0,   0,   0);
var rgbRed   = new RGBColor(255,   0,   0);
var rgbGreen = new RGBColor(  0, 255,   0);
var rgbBlue  = new RGBColor(  0,   0, 255);

var rgbSilver  = RGBColor.fromHex("#C0C0C0");
var rgbGray    = RGBColor.fromHex("#808080");
var rgbMaroon  = RGBColor.fromHex("#800000");
var rgbPurple  = RGBColor.fromHex("#800080");
var rgbFuchsia = RGBColor.fromHex("#FF00FF");
var rgbLime    = RGBColor.fromHex("#008000");
var rgbOlive   = RGBColor.fromHex("#808000");
var rgbYellow  = RGBColor.fromHex("#FFFF00");
var rgbNavy    = RGBColor.fromHex("#000080");
var rgbTeal    = RGBColor.fromHex("#008080");
var rgbAqua    = RGBColor.fromHex("#00FFFF");

/**********************************************************************
 * TimerInterval
 *
 * Creation of an object of this class kicks off a timer that fires every
 * "delay" milliseconds.  When it fires, it invokes the given "method" of
 * the given "target" object, passing it this TimerInterval as an argument.
 * That method needs to return true if it wishes the timer to fire again
 * (after another "delay" milliseconds).  If false is returned, the timer
 * is removed.
 **********************************************************************/
function TimerInterval(target, method, delay)
{
  this.target = target;
  this.method = method;
  this.delay  = delay;

  // The numeric ID of this interval
  this.id = TimerInterval.getNextIntervalID();

  // The system interval ID returned by window.setInterval()
  this.interval = null;

  // Function invoked every time the timer is fired
  this.fire = function() {
    var result = this.target[this.method](this);
    if (!result)
      TimerInterval.endInterval(this);
  }

  // The interval is started immediately after it is created.  Since every
  // interval is saved as a class property (see below), you should not save
  // a reference to it yourself.
  TimerInterval.startInterval(this);
}

TimerInterval.nextIntervalID = 1;

TimerInterval.getNextIntervalID = function() {
  var next;
  while (true) {
    next = TimerInterval.nextIntervalID++;
    if (!TimerInterval["interval"+next])
      break;
  }
  return next;
}

TimerInterval.startInterval = function(ti) {
  TimerInterval["interval"+ti.id] = ti;
  ti.interval = setInterval("TimerInterval.runInterval("+ti.id+")", ti.delay);
}

TimerInterval.endInterval = function(ti) {
  clearInterval(ti.interval);
  delete TimerInterval["interval"+ti.id];
}

TimerInterval.runInterval = function(id) {
  var ti = TimerInterval["interval"+id];
  if (ti)
    ti.fire();
}

/**********************************************************************
 * RemoteRequest - a wrapper class for the XMLHttpRequest object
 **********************************************************************/
function RemoteRequest() {
  this.req         = null;  // the underlying XMLHttpRequest object
  this.asynctarget = null;  // an object having a remoteFinished() method

  if (typeof(XMLHttpRequest) == "function" ||
      typeof(XMLHttpRequest) != "undefined")
    this.req = new XMLHttpRequest();
  else if (typeof(ActiveXObject) == "function")
    this.req = new ActiveXObject("Msxml2.XMLHTTP");

  // Utility function to register a completion callback
  this.registerCallback = function(callback) {
    if (!callback)
      this.asynctarget = null;
    else {
      this.asynctarget = callback;
      this.onreadystatechange = function() {
        if (this.req.readyState == 4)
          this.asynctarget.remoteFinished(this);
      }
    }
  }

  // Retrieve the given URL via GET.  If callback is not null, it should be
  // an object having a remoteFinished() function, which will be called on
  // download completion.  If callback is null, this function does not
  // return until download is finished.  Returns the XMLHttpRequest object
  // (NOT this object).
  this.get = function(url, callback) {
    if (!this.req)
      return;
    this.registerCallback(callback);
    this.req.open("GET", url, callback ? true : false);
    this.req.send(null);
    return this.req;
  }

  // Retrieve the given URL via POST with the given data.  Otherwise
  // similar to the above.
  this.post = function(url, data, callback) {
    if (!this.req)
      return;
    this.registerCallback(callback);
    this.req.open("POST", url, callback ? true : false);
    this.req.send(data);
    return this.req;
  }
}

/**********************************************************************
 * A global reusable RemoteRequest used by the synchronous functions below.
 * Do not use it for asynchronous requests: create your own RemoteRequest.
 **********************************************************************/
var syncReq = new RemoteRequest();

/**********************************************************************
 * getRemoteXML - retrieve the remote document's XML synchronously
 **********************************************************************/
function getRemoteXML(url, requireSuccess) {
  syncReq.get(url);
  var rs  = syncReq.req.readyState;
  var st  = syncReq.req.status;
  if (rs != 4)
    return null;
  if (typeof(requireSuccess) == "number") {
    if (st == requireSuccess)
      return syncReq.req.responseXML;
    else
      return null;
  }
  if (requireSuccess)
    if ( (st < 200) || (st > 299) )
      return null;
  return syncReq.req.responseXML;
}

/**********************************************************************
 * getRemoteText - retrieve the remote document's text synchronously
 **********************************************************************/
function getRemoteText(url, requireSuccess) {
  syncReq.get(url);
  var rs = syncReq.req.readyState;
  var st = syncReq.req.status;
  if (rs != 4)
    return null;
  if (typeof(requireSuccess) == "number") {
    if (st == requireSuccess)
      return syncReq.req.responseText;
    else
      return null;
  }
  if (requireSuccess)
    if ( (st < 200) || (st > 299) )
      return null;
  return syncReq.req.responseText;
}

/**********************************************************************
 * cookies
 **********************************************************************/
function setCookie(name, value) {
  document.cookie = name + "=" + escape(value);
}

function setCookieFrom(name, url) {
  var txt = getRemoteText(url, true);
  if (txt)
    setCookie(name, txt);
}

function setCookieDomain(domain) {
  setCookie("domain", domain);
}

function setCookiePath(path) {
  setCookie("path", path);
}

function setCookieExpires(date) {
  document.cookie = "expires=" + date.toGMTString();
}

function getCookie(name) {
  var re_str = name + "=([^;]+)";
  var re     = new RegExp(re_str);
  var match  = re.exec(document.cookie);
  if (match)
    return match[1];
  else
    return null;
}

/**********************************************************************
 * unit
 **********************************************************************/
function unit(arg, u) {
  if (typeof(arg) == "number")
    return (arg + u);
  else
    return arg;
}

/**********************************************************************
 * core
 *
 * This function takes an existing element and gives it a number of methods
 * that add extra features, the most important being:
 *
 *  + CSS manipulation via direct method calls
 *  + simple animation effects
 *  + a unified cross-browser event handling mechanism
 *
 * The same object is returned.
 **********************************************************************/
function core(obj) {
  if (obj.iscore)
    return obj;  // redundancy check
  obj.iscore = true;

  // ********** CSS Functions **********

  // Foreground and background color: the get functions return an instance
  // of RGBColor; the set functions take either an instance of RGBColor or
  // a string.
  obj.getColor = function() {
    return RGBColor.fromString(this.style.color); }

  obj.getBGColor = function() {
    return RGBColor.fromString(this.style.backgroundColor); }

  obj.setColor = function(c) {
    this.style.color = c.toString();
  }

  obj.setBGColor = function(c) {
    this.style.backgroundColor = c.toString(); // can be "transparent"
  }

  obj.setVisible = function(v) {
    if (v)
      this.style.visibility = "visible";
    else
      this.style.visibility = "hidden";
  }

  obj.show      = function() { this.setVisible(true);  }
  obj.hide      = function() { this.setVisible(false); }
  obj.isVisible = function() { return this.style.visibility == "visible"; }

  obj.setOpacity = function(o) {
    if (isIE)
      this.style.filter =
                "progid:DXImageTransform.Microsoft.Alpha(opacity="+(o*100)+")";
    else
      this.style.opacity = o;
  }

  obj.setCanSelect = function(sel) {
    if (isIE)
      this.setAttribute("unselectable", sel ? "off" : "on", 0);
    else
      this.style.MozUserSelect = sel ? "normal" : "none";
  }

  // Strip away all possible outward protrusions
  obj.noFat = function() {
    this.style.margin      = "0px";
    this.style.padding     = "0px";
    this.style.borderWidth = "0px";
  }

  // CSS positioning
  obj.absp    = function() { this.style.position = "absolute"; }
  obj.fixp    = function() { this.style.position = "fixed";    }
  obj.relp    = function() { this.style.position = "relative"; }
  obj.staticp = function() { this.style.position = "static";   }

  // These may return NaN if the element wasn't positioned manually
  obj.getX = function() { return parseFloat(this.style.left); }
  obj.getY = function() { return parseFloat(this.style.top);  }
  obj.getZ = function() { return parseInt(this.style.zIndex); }

  // Offset coordinates and dimensions
  obj.getOx      = function() { return this.offsetLeft;   }
  obj.getOy      = function() { return this.offsetTop;    }
  obj.getOWidth  = function() { return this.offsetWidth;  }
  obj.getOHeight = function() { return this.offsetHeight; }

  obj.moveTo = function(x, y) {
    this.style.left = unit(x, "px");
    this.style.top  = unit(y, "px");
  }

  obj.moveBy = function(dx, dy) {
    this.style.left = unit(this.getOx()+dx, "px");
    this.style.top  = unit(this.getOy()+dy, "px");
  }

  obj.resizeTo = function(w, h) {
    this.style.width  = unit(w, "px");
    this.style.height = unit(h, "px");
  }

  // Set both origin and dimensions
  obj.setGeom = function(x, y, w, h) {
    this.moveTo(x, y);
    this.resizeTo(w, h);
  }

  obj.setX      = function(x) { this.style.left   = unit(x, "px"); }
  obj.setY      = function(y) { this.style.left   = unit(y, "px"); }
  obj.setZ      = function(z) { this.style.zIndex = z; }
  obj.setWidth  = function(w) { this.style.width  = unit(w, "px"); }
  obj.setHeight = function(h) { this.style.height = unit(h, "px"); }

  // Returns an object with an "x" and a "y" property
  obj.getPageXY = function() {
    var x = this.offsetLeft;
    var y = this.offsetTop;
    var p = this.offsetParent;
    while (p) {
      x += p.offsetLeft;
      y += p.offsetTop;
      p = p.offsetParent;
    }
    var xy = new Object();
    xy.x = x;
    xy.y = y;
    return xy;
  }

  obj.getPageX   = function() {
    var x = this.offsetLeft;
    var p = this.offsetParent;
    while (p) {
      x += p.offsetLeft;
      p = p.offsetParent;
    }
    return x;
  }

  obj.getPageY   = function() {
    var y = this.offsetTop;
    var p = this.offsetParent;
    while (p) {
      y += p.offsetTop;
      p = p.offsetParent;
    }
    return y;
  }

  // ********** Animation **********

  // Transform foreground color from the current color to the given
  // RGBColor using the given number of steps, with the given delay between
  // each step (in milliseconds).
  obj.fadeTo = function(rgb, steps, delay) {
    var c1 = this.getColor();
    this.fadeStepR = (rgb.r - c1.r) / steps;
    this.fadeStepG = (rgb.g - c1.g) / steps;
    this.fadeStepB = (rgb.b - c1.b) / steps;
    this.fadeSteps = steps;
    this.fadeStep  = 0;
    new TimerInterval(this, "fadeOne", delay);
  }

  // Private function used by the above.
  obj.fadeOne = function() {
    if (this.fadeStep < this.fadeSteps) {
      var c = this.getColor();
      c.adjustBy(this.fadeStepR, this.fadeStepG, this.fadeStepB);
      this.setColor(c);
      this.fadeStep++;
      return true;
    } else
      return false;
  }

  // Transform background color from the current color to the given
  // RGBColor, similar to fadeTo().
  obj.fadeBGTo = function(rgb, steps, delay) {
    var c1 = this.getBGColor();
    this.fadeBGStepR = (rgb.r - c1.r) / steps;
    this.fadeBGStepG = (rgb.g - c1.g) / steps;
    this.fadeBGStepB = (rgb.b - c1.b) / steps;
    this.fadeBGSteps = steps;
    this.fadeBGStep  = 0;
    new TimerInterval(this, "fadeBGOne", delay);
  }

  // Private function used by the above.
  obj.fadeBGOne = function() {
    if (this.fadeBGStep < this.fadeBGSteps) {
      var c = this.getBGColor();
      c.adjustBy(this.fadeBGStepR, this.fadeBGStepG, this.fadeBGStepB);
      this.setBGColor(c);
      this.fadeBGStep++;
      return true;
    } else
      return false;
  }

  // Move this element from its current position to the given new location
  // gradually via the given number of steps, with the given delay between
  // each step, in milliseconds.
  obj.slideTo = function(left, top, steps, delay) {
    this.slideToLeft = left;
    this.slideToTop  = top;
    this.slideSteps  = steps;
    this.slideDx = (left - this.getOx()) / steps;
    this.slideDy = (top  - this.getOy()) / steps;
    this.slideStep = 0;
    new TimerInterval(this, "slideOne", delay);
  }

  // Private function used by the above.
  obj.slideOne = function() {
    if (this.slideStep < this.slideSteps) {
      /* if we are about to do last step, move to the absolute
         final position specified, instead of using deltas. */
      if (this.slideStep == (this.slideSteps - 1))
        this.moveTo(this.slideToLeft, this.slideToTop);
      else
        this.moveBy(this.slideDx, this.slideDy);
      this.slideStep++;
      return true;
    } else {
      return false;
    }
  }

  // ********** Element content **********

  obj.setHTML = function(html) {
    this.innerHTML = html;
  }
 
  obj.setHTMLFrom = function(url) {
    var remtext = getRemoteText(url);
    if (remtext)
      this.innerHTML = remtext;
  }

  // ********** Unified Event Handling **********

  // This mechanism attempts to mimic the DOM2 event spec:
  //
  //   http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/
  //
  // It was originally intended to give Internet Explorer a way to handle
  // events via the addEventListener() interface, but now applies to all
  // browsers.  We simulate the addEventListener() interface by "hijacking"
  // the onxxx() event handlers.  After you core() an element and register
  // handlers for an event type using this mechanism, you can no longer use
  // the onxxx() handler for the same event type.  Because Mozilla-based
  // browsers already have an addEventListener() method for elements, we
  // use the names "addListener()" and "removeListener()" instead.

  obj.listeners = new Object();

  // Dispatch the given event
  obj.dispatch = function(e) {
    if (!e)
      e = window.event;
    var larray = this.listeners[e.type];
    if (!larray)
      return;  // no handlers for this type of event
    for (i = 0; i < larray.length; i++)
      larray[i].handleEvent(e);
  }

  // Add an event listener for the given type, which should be a string
  // indicating the type without the initial "on", e.g.: "click",
  // "mousedown", "mousemove", etc.  The second arg is an object having a
  // handleEvent() function, which is called when the event type occurs.
  // The third arg should be omitted.  You can register multiple listener
  // objects for the same type, and they will all be called.  If you add a
  // duplicate listener (the same type and listener object as one that
  // already exists), it is ignored.
  obj.addListener = function(type, listener, capture) {
    if (this.hasListener(type, listener))
      return;
    if (!this.listeners[type]) {
      this.listeners[type] = new Array();
      this["on"+type] = function(e) { this.dispatch(e); }
    }
    this.listeners[type].push(listener);
  }

  // Remove the given event listener object, which should have been
  // previously registered for the given type.
  obj.removeListener = function(type, listener, capture) {
    if (!this.listeners[type])
      return;
    var larray = this.listeners[type];
    var narray = new Array();
    for (i = 0; i < larray.length; i++)
      if (larray[i] != listener)
        narray.push(larray[i]);
    this.listeners[type] = narray;
  }

  // True if the given listener already exists
  obj.hasListener = function(type, listener) {
    var larray = this.listeners[type];
    if (!larray)
      return false;
    for (i = 0; i < larray.length; i++)
      if (larray[i] == listener)
        return true;
    return false;
  }

  // If you do not otherwise assign a handleEvent() method to this element,
  // the following is assigned for you by default.  It invokes the function
  // doxxx() on this object (remember that onxxx() are disabled in this
  // case).  E.g., to handle a click on a core()'ed element "elem", use the
  // following code:
  //
  //   elem.addListener("click", elem);
  //   elem.doclick = function(event) { alert("I was clicked!"); }
  obj.handleEvent = function(event) {
    if (this["do"+event.type])
      this["do"+event.type](event);
  }

  // ========== End of core() ==========
  return obj;
}

/**********************************************************************
 * Make the given object capable of listening to events using the unified
 * method implemented by core(), i.e., by dispatching event types to
 * doxxx() functions defined in the object itself.  The object given can be
 * an arbitrary JavaScript object, not necessarily an element.
 **********************************************************************/
function makeListener(obj) {
  obj.handleEvent = function(event) {
    if (this["do"+event.type])
      this["do"+event.type](event);
  }
}

/**********************************************************************
 * Return the element with the given id, or null if not found.  The element
 * is also core()'ed if necessary.
 **********************************************************************/
function get(id) { 
  var obj = document.getElementById(id);
  if (obj)
    return core(obj);
  else
    return null;
}

/**********************************************************************
 * Create a new element of the given tag, placing it within the given
 * parent.  The element is immediately core()'ed.  If the id arg is also
 * given, the new element's id is set to the given id.
 **********************************************************************/
function create(tag, parent, id) {
  var elem = document.createElement(tag);
  if (!elem)
    return null;
  core(elem);
  parent.appendChild(elem);
  if (id)
    elem.id = id;
  return elem;
}

/**********************************************************************
 * Get the dimensions of the current document
 **********************************************************************/
function getBodyWidth() {
  return document.body.clientWidth;
}

function getBodyHeight() {
  return document.body.clientHeight;
}

/**********************************************************************
 * Event processing
 **********************************************************************/
function evTarget(e) {
  if (defined(e.srcElement)) return e.srcElement;
  if (defined(e.target))     return e.target;
  return null;
}

function evPageX(e) {
  if (defined(e.pageX))   return e.pageX;
  if (defined(e.clientX)) return e.clientX;
  return null;
}

function evPageY(e) {
  if (defined(e.pageY))   return e.pageY;
  if (defined(e.clientY)) return e.clientY;
  return null;
}

function evStopPropagation(e) {
  if (defined(e.stopPropagation))
    e.stopPropagation();
  else if (defined(e.cancelBubble))
    e.cancelBubble = true;
}

function evStop(e) { evStopPropagation(e); }
