///////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2006 Team and Concepts Limited
// EditGrid Javascript Standard Library
//
// vim:set autoindent shiftwidth=4 tabstop=4 noexpandtab:

var std = {};

// alias for the top level context, e.g. window object in a browser
std.global = this;

// from prototype.js
std._scriptFragment = '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)';
std._emptyFunction = function() {}
std._valueFunction = function(x) { return x; }

std.keytable = {
	BACKSPACE: [8, 'Backspace'],
	TAB: [9, 'Tab'],
	ENTER: [13, 'Enter'],
	SHIFT: [16, 'Shift'],
	CTRL: [17, 'Ctrl'],
	ALT: [18, 'Alt'],
	PAUSE: [19, 'Pause'],
	CAPS_LOCK: [20, 'Caps'],
	ESCAPE: [27, 'Esc'],
	SPACE: [32, 'Space'],
	PAGE_UP: [33, 'Page Up'],
	PAGE_DOWN: [34, 'Page Down'],
	END: [35, 'End'],
	HOME: [36, 'Home'],
	LEFT_ARROW: [37, 'Left'],
	UP_ARROW: [38, 'Up'],
	RIGHT_ARROW: [39, 'Right'],
	DOWN_ARROW: [40, 'Down'],
	INSERT: [45, 'Ins'],
	DELETE: [46, 'Del'],
	LEFT_WINDOW: [91, ''],
	RIGHT_WINDOW: [92, ''],
	SELECT: [93, ''],
	NUM_LOCK: [144, ''],
	SCROLL_LOCK: [145, ''],
	';': [186, ';'],
	'=': [187, '='],
	',': [188, ','],
	'.': [190, '.'],
	'/': [191, '/'],
	'`': [192, '`'],
	'[': [219, '['],
	"\\": [220, "\\"],
	']': [221, ']'],
	"'": [222, "'"],
	CMD: [224, ''],
	IME: [229, '']
};

for (var i = 48; i <= 57; i++) std.keytable[String.fromCharCode(i)] = [i, String.fromCharCode(i)];
for (var i = 65; i <= 90; i++) std.keytable[String.fromCharCode(i)] = [i, String.fromCharCode(i)];
for (var i = 112; i <= 123; i++) std.keytable['F' + (i-111)] = [i, 'F' + (i-111)];

////////////////////////////////////////////////////////////////////////////////
// Language Util

std.lang = {};

std.lang.merge = function(dest, src) {
    for (var name in src) {
        if (typeof(src[name]) != "undefined") dest[name] = src[name];
    }
}

std.lang.bind = function() {
	var args = std.array.from(arguments);
	var obj = args.shift();
	var method = args.shift();
	method.prototype; // assert method != null
	if (typeof method != "function") throw "Invalid method: " + method;
	return function() {
		var iargs = [];
		for (var i = 0; i < arguments.length; i++) iargs.push(arguments[i]);
		return method.apply(obj, args.concat(iargs));
	}
}

std.lang.later = function() {
	var args = std.array.from(arguments);
	var timeout = args.shift();
	var obj = args.shift();
	var method = args.shift();
	return window.setTimeout(function() { return method.apply(obj, args) }, timeout);
}

std.lang.defaultize = function(value, defaultValue) {
	return typeof(value) == 'undefined' ? defaultValue : value;
}

/*
/* Evalutes a primitive value, function and return the result.
 * - primitive value: unchanged
 * - function: evalute the function and return the result
 */
std.lang.evaluate = function(ref) {
	return typeof(ref) == 'function' ? ref() : ref;
}

std.lang.linearize = function(hash) {
	var result = [];
	for (var key in hash) result.push(hash[key]);
	return result;
}

std.lang.describe = function(hash) {
	var result = [];
	for (var key in hash) result.push(key + ": " + hash[key]);
	return result;
}

std.lang.undef = function(val) {
	return typeof(val) == 'undefined';
}

std.lang.isDef = function() {
	for (var i = 0; i < arguments.length; i++) {
		var val = arguments[i]
		if (typeof(val) == 'undefined') return false;
	}
	return true;
}

std.lang.isArray = function(val) {
	for (var i = 0; i < arguments.length; i++) {
		var val = arguments[i];
		if (typeof(val) == 'undefined' || val == null || val.constructor != Array) return false;
	}
	return true;
}

////////////////////////////////////////////////////////////////////////////////
// Array Util

std.array = {};

std.array.from = function(iterable) {
	var array = new Array();
	for (var i = 0; i < iterable.length; i++)
		array.push(iterable[i]);
	return array;
}

std.array.contains = function(array, element) {
	for (var i=0; i < array.length; i++) {
		if (array[i] == element) return true;
	}
	return false;
}

std.array.without = function(array, element, eqCallback) {
	var returnValue = new Array();
	if (eqCallback) {
		for (var i = 0; i < array.length; i++) {
			if (!eqCallback(array[i], element)) returnValue.push(array[i]);
		}
	} else {
		for (var i = 0; i < array.length; i++) {
			if (array[i] != element) returnValue.push(array[i]);
		}
	}
	return returnValue;
}

////////////////////////////////////////////////////////////////////////////////
// Functors

std.functor = {};

std.functor.not = function(func) {
	return function() { return !func() }
}

std.functor.and = function() {
	var funcs = std.array.from(arguments);
	return function() {
		for (var i = 0; i < funcs.length; i++) {
			if (!funcs[i]()) return false;
		}
		return true;
	}
}

std.functor.or = function() {
	var funcs = std.array.from(arguments);
	return function() {
		for (var i = 0; i < funcs.length; i++) {
			if (funcs[i]()) return true;
		}
		return false;
	}
}

////////////////////////////////////////////////////////////////////////////////
// j-expressions
//   a jexpr is a boolean expression in JSON format, e.g.
//   boolean NOT: { not: expr }
//   boolean AND: { and: [expr, ...] }
//   boolean OR : { or: [expr, ...] } or simply [expr, ...]
//   where an expr can be a primitive type, a function, or another jexpr

std.jexpr = {};

std.jexpr.evaluate = function(jexpr) {

	var jexprtype = typeof(jexpr);

	if (jexprtype == 'function') {

		return jexpr.call();

	} else if (jexprtype == 'number' || jexprtype == 'boolean' || jexprtype == 'string' || jexprtype == 'undefined' || jexpr == null) {

		return jexpr;

	} else if (typeof(jexpr.not) != 'undefined') {

		return !std.jexpr.evaluate(jexpr.not);

	} else if (typeof(jexpr.and) == 'object') {

		jexpr = jexpr.and;

		for (var i = 0; i < jexpr.length; i++) {
			if (!std.jexpr.evaluate(jexpr[i])) return false;
		}

		return true;

	} else {

		jexpr = typeof(jexpr.or) == 'object' ? jexpr.or : jexpr;

		for (var i = 0; i < jexpr.length; i++) {
			if (std.jexpr.evaluate(jexpr[i])) return true;
		}

		return false;

	}
}

std.jexpr.functor = function(jexpr) {
	return function() {	return std.jexpr.evaluate(jexpr) }
}

////////////////////////////////////////////////////////////////////////////////
// List Util

std.list = {};

std.list.iterate = function(list, functor) {
	for (var i = 0; i < list.length; i++) functor(list[i], i);
}

std.list.map = function(list, functor) {
	var array = new Array();
	for (var i = 0; i < list.length; i++) array.push(functor(list[i], i));
	return array;
}

std.list.filter = function(list, functor) {
	var array = new Array();
	for (var i=0; i < list.length; i++) { if (functor(list[i], i)) array.push(list[i]) };
	return array;
}

std.list.max = function(list, functor) {
	var max = Number.NEGATIVE_INFINITY;
	for (var i = 0; i < list.length; i++) {
		if (list[i] != null)
			max = Math.max(max, functor(list[i]));
	}
	return max;
}

std.list.min = function(list, functor) {
	var min = Number.POSITIVE_INFINITY;
	for (var i = 0; i < list.length; i++) {
		if (list[i] != null)
			min = Math.min(min, functor(list[i]));
	}
	return min;
}

std.list.find = function(list, functor) {
	for (var i = 0; i < list.length; i++) {
		if (functor(list[i], i)) return list[i];
	}
	return null;
}

std.list.findAll = function(list, functor) {
	var result = [];
	for (var i = 0; i < list.length; i++) {
		if (functor(list[i], i)) result.push(list[i]);
	}
	return result;
}

std.list.findIndex = function(list, element) {
	for (var i = 0; i < list.length; i++) {
		if (list[i] == element) return i;
	}
	return -1;
}

////////////////////////////////////////////////////////////////////////////////
// Hash Util

std.hash = {};

std.hash.merge = function() {
	// merging arguments with right precendence
	var hashes = std.array.from(arguments);
	var dest = {};
	for (var i = 0; i < hashes.length; i++) {
		var hash = hashes[i];
		for (var name in hash) dest[name] = hash[name];
	}
	return dest;
}

////////////////////////////////////////////////////////////////////////////////
// Math Function

std.math = {};

std.math.compareQuad = function(a1, a2, b1, b2) {
	var res = {};
	if (a1[0] == b1[0] && a1[1] == b1[1] && a2[0] == b2[0] && a2[1] == b2[1]) {
		res.enclose = 0;
		res.overlap = 2;
	} else if (a2[0] < b1[0] || a1[0] > b2[0] || a2[1] < b1[1] || a1[1] > b2[1]) {
		res.enclose = 0;
		res.overlap = 0;
	} else if (a1[0] <= b1[0] && a1[1] <= b1[1] && a2[0] >= b2[0] && a2[1] >= b2[1]) {
		res.enclose = 1;
		res.overlap = 1;
	} else if (a1[0] >= b1[0] && a1[1] >= b1[1] && a2[0] <= b2[0] && a2[1] <= b2[1]) {
		res.enclose = -1;
		res.overlap = 1;
	} else {
		res.enclose = 0;
		res.overlap = 1;
	}
	return res;
}

// example use: finding the best position and alignment to place the context menu
// x,y: mouse position; w,h: size of the context menu to display, W,H: size of the viewport

std.math.calcPointAlign = function(x, y, w, h, W, H) {
	
	var align = { 'bottom': 0, 'top': 0, 'right': 0, 'left': 0 };
	var spcBelow = H - (y + h);
	var spcAbove = y - h;
	var spcRight = W - (x + w);
	var spcLeft  = x - w;

	align[(spcBelow < 0 && spcAbove > spcBelow) ? 'bottom' : 'top' ] = 1;
	align[(spcRight < 0 && spcLeft  > spcRight) ? 'right'  : 'left'] = 1;
	return align;
}

std.math.calcPointPlacement = function(x, y, w, h, W, H) {
	var align = std.math.calcPointAlign(x, y, w, h, W, H);
	return { align: align, x: x - align.right * w, y: y - align.bottom * h };
}

// example use: finding the best position to show typical popup menu, but doesn't deal with offsets
// b*: bounds of the original menu item which the popup will align with
// w,h: size of the menu to display, W,H: size of the viewport
// horiz: align horizontally (popup from menubar), or aliign vertically (popup from submenu)

std.math.calcBoxAlign = function(bx, by, bw, bh, w, h, W, H, horiz) {
	
	var hori = horiz ? 1 : 0;
	var vert = horiz ? 0 : 1;

	var align = { 'bottom': 0, 'top': 0, 'right': 0, 'left': 0 };
	var spcBelow = H - (by + (bh * hori) + h);
	var spcAbove = by + (bh * vert) - h;
	var spcRight = W - (bx + (bw * vert) + w);
	var spcLeft  = bx + (bw * hori) - w;

	align[(spcBelow < 0 && spcAbove > spcBelow) ? 'bottom' : 'top'] = 1;
	align[(spcRight < 0 && spcLeft  > spcRight) ? 'right'  :'left'] = 1;
	return align;
}

std.math.calcBoxPlacement = function(bx, by, bw, bh, w, h, W, H, horiz) {
	var align = std.math.calcBoxAlign(bx, by, bw, bh, w, h, W, H, horiz);
	if (horiz)
		return { align: align, x: bx + align.right * (bw - w), y: by + align.top * bh - align.bottom * h };
	else
		return { align: align, x: bx + align.left * bw - align.right * w, y: by + align.bottom * (bh - h) };
}

////////////////////////////////////////////////////////////////////////////////
// String Util (from prototype.js)

std.string = {};

std.string.enclose = function(text, start, end, bool) {
	return bool ? start + text + end : text;
}

std.string.stripTags = function(text) {
	return text.replace(/<\/?[^>]+>/gi, '');
}

std.string.stripScripts = function(text) {
	return text.replace(new RegExp(std._scriptFragment, 'img'), '');
}

std.string.extractScripts = function(text) {
	var matchAll = new RegExp(std._scriptFragment, 'img');
	var matchOne = new RegExp(std._scriptFragment, 'im');
	return std.list.map(text.match(matchAll) || [], function(scriptTag) {
		return (scriptTag.match(matchOne) || ['', ''])[1];
	});
}

std.string.evalScripts = function(text) {
	return std.list.map(std.string.extractScripts(text), function(element) { eval(element) });
}

std.string.escapeHTML = function (s) {
    return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}

std.string.fullEscapeHTML = function (s) {
    return std.string.escapeHTML(s).replace(/\n/g, '<br/>').replace(/  /g, '&nbsp; ');
}

std.string.autoLink = function(s) {
	s = s.replace(/([\d\w-+]+:\/\/[\S]+)/g, '<a href="$1" target="_blank" title="link">$1</a>');
	s = s.replace(/([\d\w-+]+@[\d\w-]+\.[\d\w-\.]{2,})/g, '<a href="mailto:$1" title="email">$1</a>');
	return s;
}

std.string.unescapeHTML = function(text) {
	var div = document.createElement('div');
	div.innerHTML = std.string.stripTags(text);
	return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
}

std.string.toArray = function(text) {
	return text.split('');
}

std.string.camelize = function(text) {
    var oStringList = text.split('-');
    if (oStringList.length == 1) return oStringList[0];

    var camelizedString = text.indexOf('-') == 0
		? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
		: oStringList[0];

    for (var i = 1, len = oStringList.length; i < len; i++) {
		var s = oStringList[i];
		camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
    }

    return camelizedString;
}

std.string.capitalize = function(text) {
	return text.charAt(0).toUpperCase() + text.substring(1);
}

std.string.trim = function(text) {
	return text.replace(/^\s*(.*)\s*$/, "$1");
}

std.string.parsePosition = function(s) {
	s = s.split(' ');
	p = { top: 0, bottom: 0, left: 0, right: 0 };
	for (var i = 0; i < s.length; i++) {
		switch (s[i]) {
			case 'top': if (!p.bottom) p.top = 1; break;
			case 'bottom': if (!p.top) p.bottom = 1; break;
			case 'left': if (!p.right) p.left = 1; break;
			case 'right': if (!p.left) p.right = 1; break;
		}
	}
	return p;
}

std.string.printPosition = function(p) {
	s = [];
	if (p.top) s.push('top');
	else if (p.bottom) s.push('bottom');
	if (p.left) s.push('left');
	else if (p.right) s.push('right');
	return s.join(' ');
}

std.string.times = function(s, times) {
	for (var i=1; i<times; i<<=1)
		s = s + s;
	return s.substr(0, times);
}

std.string.lpad = function(s, len, c) {
	var padding = std.string.times(c, len - s.length);
	return padding + s;
}

std.string.right = function (s, len) {
	return s.length < len ? s : s.substr(s.length - len, len);
}

////////////////////////////////////////////////////////////////////////////////
// Browser Detection

std.browser = { isFF: null, isIE: null, isMoz: null, isOpera: null, isAppleWebKit: null, isSupport: null, major: null, minor: null, gecko: null, isSafariWin: null, isSafariMobile: null };

std.browser.platform = {};
std.browser.applewebkit = {};
std.browser.applewebkit.parseVersion = function(version) {
  var bits = version.split(".");
  var is_nightly = (version[version.length - 1] == "+");
  if (is_nightly) {
    var minor = "+";
  } else {
    var minor = parseInt(bits[1]);
    // If minor is Not a Number (NaN) return an empty string
    if (isNaN(minor)) {
      minor = "";
    }
  }
  return {major: parseInt(bits[0]), minor: minor, is_nightly: is_nightly};
}

std.browser.applewebkit.getVersion = function() {
  var regex = new RegExp("\\(.*\\) AppleWebKit/(.*) \\((.*)");
  var matches = regex.exec(navigator.userAgent);
  if (matches) {
    var webkit_version = std.browser.applewebkit.parseVersion(matches[1]);    
  } 
  return {major: webkit_version['major'], minor: webkit_version['minor'], is_nightly: webkit_version['is_nightly']};
}

{
	var i, res;
	var ua = navigator.userAgent;
	if ((std.browser.isOpera = (i = ua.indexOf('Opera')) >= 0)) {
		std.browser.major = parseFloat(ua.substr(i + 6));
	} else if ((std.browser.isAppleWebKit = ((i = ua.indexOf('AppleWebKit')) >= 0))) {
		var webkitVersion = std.browser.applewebkit.getVersion();
		std.browser.major = webkitVersion['major'];
		std.browser.minor = webkitVersion['minor'];
		std.browser.isSafariWin = navigator.platform.indexOf('Win') == 0;
		std.browser.isSafariMobile = (ua.indexOf('Mobile') != -1);
	} else if ((std.browser.isIE = (i = ua.indexOf('MSIE ')) >= 0)) {
		std.browser.major = parseFloat(ua.substr(i + 5));
	} else if (std.browser.isMoz = (res = /^Mozilla.+ rv:([0-9]+\.[0-9]+)(\.[0-9]+(?:\.[0-9]+)?)?/.exec(ua)) != null) {
		std.browser.major = res[1];
		std.browser.minor = res[2];
		std.browser.isFF = ua.indexOf('Firefox/') != -1;
		res = /Gecko\/([0-9]+)/.exec(ua);
		std.browser.gecko = res[1];
	}
	std.browser.platform.isWin   = navigator.platform.indexOf('Win') == 0;
	std.browser.platform.isMacOS = (ua.indexOf("Mac OS") != -1);
	std.browser.isSupport = (std.browser.isIE && std.browser.major >= 6)
		|| (std.browser.isMoz && std.browser.major >= 1.7 && std.browser.gecko != "20060127")
		|| (std.browser.isOpera && std.browser.major >= 9)
		|| (std.browser.isAppleWebKit && std.browser.major >= 3);
}

std.browser.no = {};
std.browser.buggy = {};

(std.browser.recalcRules = function() {
	if (std.browser.isSafariMobile) {
		std.browser.no.ondblclick = true;
	}

})();

////////////////////////////////////////////////////////////////////////////////
// Cross-browser Interaction to Host Environment

std.host = {};

std.host.bookmark = function(title, url) {
	if (window.sidebar) { // FireFox
		window.sidebar.addPanel(title, url, "");
	} else if (window.external) { // IE
		window.external.AddFavorite(url, title);
	} else if (window.opera && window.print) { // Opera Hotlist
		return true;
	}
}

std.host.open = function(url, popup) {
	if (popup) {
		window.open(url)
	} else {
		window.location.href = url;
	}
}

std.host.reload = function() {
	window.location.href = window.location.href;
}

////////////////////////////////////////////////////////////////////////////////
// DOM Manipulation

std.dom = {};

std.dom.element = function(element) {
	if (element) {
		return typeof(element) == 'string' ? document.getElementById(element) : element;
	} else {
		return null;
	}
}

std.dom.remove = function(element) {
	element = std.dom.element(element);
	element.parentNode.removeChild(element);
}

std.dom.update = function(element, html) { // from prototype.js
	std.dom.element(element).innerHTML = std.string.stripScripts(html);
	setTimeout(function() { std.string.evalScripts(html) }, 10);
}

std.dom.getDimensions = function(element) {
	element = std.dom.element(element);
	
	if (element == document.body)
		return std.dom.getBodyExtent();

	if (std.css.getStyle(element, 'display') != 'none')
		return {width: element.offsetWidth, height: element.offsetHeight};

	// All *Width and *Height properties give 0 on elements with display none,
	// so enable the element temporarily
	var els = element.style;
	var originalVisibility = els.visibility;
	var originalPosition = els.position;
	els.visibility = 'hidden';
	els.position = 'absolute';
	els.display = '';
	var originalWidth = element.clientWidth;
	var originalHeight = element.clientHeight;
	els.display = 'none';
	els.position = originalPosition;
	els.visibility = originalVisibility;
	return {width: originalWidth, height: originalHeight};
}

std.dom.newElement = function(tagName, parentNode, attrs, styles) {

	var key = tagName.toLowerCase ();
	var cnt = std.perf.newElementCount;
	if (cnt[key] == null)
		cnt[key] = 0;
	
	cnt[key]++;

	var ele = document.createElement(tagName);
	if (attrs) {
		for (var key in attrs) ele[key] = attrs[key];
	}
	if (styles) std.dom.setStyles(ele, styles);
	if (parentNode) parentNode.appendChild(ele);
	return ele;
}

std.dom.newDiv = function(parentNode, attrs, styles) {
	return std.dom.newElement('div', parentNode, attrs, styles);
}

std.dom.newText = function(data, parentNode) {
	var node = document.createTextNode(data);
	if (parentNode) parentNode.appendChild(node);
	return node;
}

std.dom.newComment = function(data, parentNode) {
	var node = document.createComment(data);
	if (parentNode) parentNode.appendChild(node);
	return node;
}

std.dom.newTree = function(html) {
	var node = std.dom.newElement('div');
	node.innerHTML = html;
	return node.firstChild;
}

std.dom.parentForm = function(element) {
	element = std.dom.element(element);
	while (element.parentNode) {
		element = element.parentNode;
		if (element.tagName.toLowerCase() == 'form') {
			return element;
		}
	}
	return null;
}

/*
 * Backtrack from the element up the DOM tree, until func return false
 * @return the last transvered element, or null if passed through the root node
 */
std.dom.backtrack = function(element, func) {
	element = std.dom.element(element);
	while (element && func(element))
		element = element.parentNode;
	return element;
}

std.dom.show = function(element) {
	element = std.dom.element(element);
	element.style.display="";
	element._showAtTime = new Date();
}

std.dom.hide = function(element, minMs) {
	var now = new Date();
	minMs = minMs || 0;
	element = std.dom.element(element);
	if (minMs > 0) {
		var showAtTime = element._showAtTime || 0;
		var shownTime = now - element._showAtTime;
		if (shownTime >= minMs)
			element.style.display = "none";
		else {
			setTimeout(std.lang.bind(null, std.dom.hide, element, 0), minMs - shownTime);
		}
	}
	else {
		element.style.display = "none";
	}
}

std.dom.toggleShowHide = function(element, minMs) {
	element = std.dom.element(element);
	if (element.style.display == "none") {
		this.show(element);
	} else {
		this.hide(element, minMs);
	}
}

// DOM Builder
std.dom.Builder = function(initNode) {
	this.current = initNode;
}
std.dom.Builder.prototype.push = function(tagName, attrs, styles) {
	return this.current = std.dom.newElement(tagName, this.current, attrs, styles);
}
std.dom.Builder.prototype.pushElement = function(element) {
	this.current.appendChild(element);
	return this.current = element;
}
std.dom.Builder.prototype.pop = function() {
	var node = this.current;
	this.current = this.current.parentNode;
	return node;
}
std.dom.Builder.prototype.peek = function() {
	return this.current;
}
std.dom.Builder.prototype.next = function(tagName, attrs, styles) {
	this.pop();
	return this.push(tagName, attrs, styles);
}
std.dom.Builder.prototype.nextElement = function(element) {
	this.pop();
	return this.pushElement(element);
}
std.dom.Builder.prototype.append = function(tagName, attrs, styles) {
	return std.dom.newElement(tagName, this.current, attrs, styles);
}
std.dom.Builder.prototype.appendElement = function(element) {
	this.current.appendChild(element);
	return element;
}
std.dom.Builder.prototype.reset = function() {
	this.current = null;
}

// Short-hands for applying styles
std.dom.setStyles = function(node, styles) {
	for (var key in styles) node.style[key] = styles[key];
}

// Cross-browser: add option to select box
std.dom.addOption = function(select, option) {
	if (select.options && select.options.add) {
		select.options.add(option);
	} else {
		select.add(option, null);
	}
}

std.dom.clearOptions = function(select) {
	while (select.length)
		select.remove(0);
}

// Clear childen of a node, optimized for speed
std.dom.clearChildren = function(node) {
	if (std.browser.isIE) { // IE throws exception when setting innerHTML to null in TABLE, TBODY, etc
		var child;
		while (child = node.lastChild) node.removeChild(child);
	} else {
		node.innerHTML = '';
	}
}

std.dom.appendChildren = function(node, children) {
	if (!std.lang.isArray(children))
		children = [ children ];
	for (var i = 0; i < children.length; i++)
		node.appendChild(children[i]);
}

std.dom.setChildren = function(node, children) {
	std.dom.clearChildren(node);
	std.dom.appendChildren(node, children);
}

// Cross-browser opacity setting
std.dom.opacity = function(element, op) {
	element = std.dom.element(element);
    if (std.browser.isIE) {
        element.style.filter = op == 1 ? '' : 'progid:DXImageTransform.Microsoft.Alpha(opacity=' + parseInt(op * 100) + ')';
    } else {
        element.style.opacity = op;
    }
}

// Extraction of node attachment points from a node tree
std.dom.extractAttachmentPoints = function(root, customAttributeName) {
	root = std.dom.element(root);
	var attrName = customAttributeName ? customAttributeName : 'attachPoint';
	var nodeList = root.getElementsByTagName('*');
	var map = {};
	for (var i = nodeList.length - 1; i >= 0; i--) {
		var node = nodeList.item(i);
		var attr = node.getAttribute(attrName);
		if (attr) {
			if (map[attr]) {
				if (map[attr] instanceof Array)
					map[attr].push(node);
				else map[attr] = [map[attr], node];
			} else map[attr] = node;
		}
	}
	if (root.getAttribute(attrName))
		map[root.getAttribute(attrName)] = root;
	return map;
}

std.dom.getEventTarget = function(e) {
	var e = e ? e : window.event;
	return e.target ? e.target : e.srcElement;
}

std.dom.getTargetPos = function(target) {
	var pos = { x: 0, y: 0 };
    if (target.x && target.y) {
		pos.x += target.x;
		pos.y += target.y;
    } else {
		do {
			pos.x += target.offsetLeft || 0;
			pos.y += target.offsetTop || 0;
			target = target.offsetParent;
			if (target && target != document.body) {
				pos.x -= target.scrollLeft;
				pos.y -= target.scrollTop;
			}
		} while (target);
    }
    return pos;
}

std.dom.getBounds = function(node) {
	var pos = std.dom.getTargetPos(node);
	var size = std.dom.getDimensions(node);
	return {
		top: pos.y,
		left: pos.x,
		bottom: pos.y + size.height - 1,
		right: pos.x + size.width - 1,
		height: size.height,
		width : size.width
	};
}

std.dom.getEventPos = function(e) {
	return std.dom.getTargetPos(std.dom.getEventTarget(e));
}

std.dom.getOffsetMid = function(node) {
	return node.offsetLeft + parseInt(node.offsetWidth / 2);
}

std.dom.getOffset = function(node) {
	return node.offsetLeft;
}

std.dom.getBodyExtent = function () {
	// works even for zoomed viewport
	if (std.browser.isSafariMobile) {
		return { width: window.innerWidth, height: window.innerHeight };
	} else {
		if (!std.dom._bodyExtendProbe) {
			std.dom._bodyExtendProbe = std.dom.newElement('div', document.body, null, { position: 'absolute', top: '100%', left: '100%', visibility: 'hidden' });
		}
		var pos = std.dom.getTargetPos(std.dom._bodyExtendProbe);
		return {
			width: pos.x,
			height: pos.y
		};
	}
}

std.dom.noselect = function(node) {
	node.style.MozUserSelect = "none";
	node.unselectable = "on";
}

////////////////////////////////////////////////////////////////////////////////
// Cross-browser Range Manipulation

std.range = {};

std.range.moveCaretEnd = function(element) {
	if (element.setSelectionRange) { // Moz
		element.focus();
		element.setSelectionRange(element.value.length, element.value.length);
	} else if (element.createTextRange) { // IE
		var range = element.createTextRange();
		range['mo'+'ve']('textedit', 1);
		range.select();
	} else {
		throw "No suitable range impl";
	}
}

std.range.moveCaretBegin = function(element) {
	if (element.setSelectionRange) { // Moz
		element.focus();
		element.setSelectionRange(0, 0);
	} else if (element.createTextRange) { // IE
		var range = element.createTextRange();
		range['mo'+'ve']('textedit', -1);
		range.select();
	} else {
		throw "No suitable range impl";
	}
}

std.range.getInputSelection = function(input) {
	var ret = { start: 0, end: 0, size: 0 };
	if (input.selectionStart || input.selectionStart == '0') { // Mozilla
		ret.start = input.selectionStart;
		ret.end = input.selectionEnd;
		ret.size = ret.end - ret.start;
	} else if (document.selection) { // IE
		var sel = document.selection.createRange(); // current selection
		var full = sel.duplicate();
		if (input.tagName == 'INPUT')
			full = input.createTextRange();
		else
			full.moveToElementText(input); // full selection of input
		for (ret.size = 0; sel.compareEndPoints('EndToStart', sel) == 1; ++ret.size)
			sel.moveEnd('character', -1);
		for (ret.start = 0; sel.compareEndPoints('StartToStart', full) == 1; ++ret.start) {
			sel.moveStart('character', -1);
		}
		ret.end = ret.start + ret.size;
	} else {
		throw "No suitable range impl";
	}
	return ret;
}

std.range.insertAtCursor = function(input, str) {
	if (document.selection) {
		var sel = document.selection.createRange();
		sel.text = str;
		if (std.browser.isOpera && std.browser.platform.isWin) {
			input.selectionStart += str.replace('\n', '\r\n').length;
		} else {
			sel.select();
		}
	} else if (input.selectionStart || input.selectionStart == '0') {
		var start = input.selectionStart;
		input.value = input.value.substring(0, start) + str + input.value.substring(input.selectionEnd, input.value.length);
		input.selectionStart = start += str.length;
		input.selectionEnd = start;
	} else {
		input.value += str;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Delegates

std.func = {};

std.func.Delegate = function() {
	this.ctx = [];
	this.fun = [];
}

std.func.Delegate.prototype.add = function(ctx, fun) {
	this.ctx.push(ctx);
	this.fun.push(fun);
	return this;
}

std.func.Delegate.prototype.call = function() {
	var args = std.array.from(arguments);
	for (var i=0; i<this.fun.length; ++i)
		this.fun[i].apply(this.ctx[i], args);
}

////////////////////////////////////////////////////////////////////////////////
// Event Manipulation (from prototype.js)

std.event = {
	KEY_BACKSPACE: 8,
	KEY_TAB:       9,
	KEY_RETURN:   13,
	KEY_ESC:      27,
	KEY_LEFT:     37,
	KEY_UP:       38,
	KEY_RIGHT:    39,
	KEY_DOWN:     40,
	KEY_DELETE:   46
};

std.event.KeyCodes = {};

std.event.KeyCodes.generic = {
	KEY_BACKSPACE: 8,
	KEY_TAB: 9,
	KEY_ENTER: 13,
	KEY_SHIFT: 16,
	KEY_CTRL: 17,
	KEY_ALT: 18,
	KEY_PAUSE: 19,
	KEY_CAPS_LOCK: 20,
	KEY_ESCAPE: 27,
	KEY_SPACE: 32,
	KEY_PAGE_UP: 33,
	KEY_PAGE_DOWN: 34,
	KEY_END: 35,
	KEY_HOME: 36,
	KEY_LEFT_ARROW: 37,
	KEY_UP_ARROW: 38,
	KEY_RIGHT_ARROW: 39,
	KEY_DOWN_ARROW: 40,
	KEY_INSERT: 45,
	KEY_DELETE: 46,
	KEY_LEFT_WINDOW: 91,
	KEY_RIGHT_WINDOW: 92,
	KEY_SELECT: 93,
	KEY_F1: 112,
	KEY_F2: 113,
	KEY_F3: 114,
	KEY_F4: 115,
	KEY_F5: 116,
	KEY_F6: 117,
	KEY_F7: 118,
	KEY_F8: 119,
	KEY_F9: 120,
	KEY_F10: 121,
	KEY_F11: 122,
	KEY_F12: 123,
	KEY_NUM_LOCK: 144,
	KEY_SCROLL_LOCK: 145,
	KEY_CMD: 224,
	KEY_IME: 229
};

std.event.KeyCodes.webkit418 = {
	KEY_BACKSPACE: 8,
	KEY_TAB: 9,
	KEY_ENTER: 13,
	KEY_SHIFT: 16,
	KEY_CTRL: 17,
	KEY_ALT: 18,
	KEY_PAUSE: 63250,
	KEY_CAPS_LOCK: 20,
	KEY_ESCAPE: 27,
	KEY_SPACE: 32,
	KEY_PAGE_UP: 63276,
	KEY_PAGE_DOWN: 63277,
	KEY_END: 63275,
	KEY_HOME: 63273,
	KEY_LEFT_ARROW: 63234,
	KEY_UP_ARROW: 63232,
	KEY_RIGHT_ARROW: 63235,
	KEY_DOWN_ARROW: 63233,
	KEY_INSERT: 63302,
	KEY_DELETE: 63272,
	KEY_LEFT_WINDOW: 91,
	KEY_RIGHT_WINDOW: 92,
	KEY_SELECT: 93,
	KEY_F1: 63236,
	KEY_F2: 63237,
	KEY_F3: 63238,
	KEY_F4: 63239,
	KEY_F5: 63240,
	KEY_F6: 63241,
	KEY_F7: 63242,
	KEY_F8: 63243,
	KEY_F9: 63244,
	KEY_F10: 63245,
	KEY_F11: 63246,
	KEY_F12: 63247,
	KEY_NUM_LOCK: 144,
	KEY_SCROLL_LOCK: 145
};

std.event.KeyMap = {
	AppleWebKit: {
		63250: 19, // pause
		63276: 33, // page up
		63277: 34, // page down
		63275: 35, // end
		63273: 36, // home
		63234: 37, // left arrow
		63232: 38, // up arrow
		63235: 39, // right arrow
		63233: 40, // down arrow
		63302: 45, // insert
		63272: 46, // delete
		63236: 112, // f1
		63237: 113, // f2
		63238: 114, // f3
		63239: 115, // f4
		63240: 116, // f5
		63241: 117, // f6
		63242: 118, // f7
		63243: 119, // f8
		63244: 120, // f9
		63245: 121, // f10
		63246: 122, // f11
		63247: 123  // f12
	}
};

std.event.getKeyCodes = function(e) {
	if (e && e.type != 'keypress')
		return std.event.KeyCodes.generic;

	if (std.browser.isAppleWebKit && !std.browser.isSafariWin) {
		return std.event.KeyCodes.webkit418;
	}
	return std.event.KeyCodes.generic;
}

std.event.getKeyCode = function (e) {
	if (std.browser.isSafariWin && std.browser.major >= 3) {
		var key  = e.keyIdentifier.toLowerCase();
		var codes = std.event.getKeyCodes(e);
		var mapping = {
			'up'       : codes.KEY_UP_ARROW,
			'down'     : codes.KEY_DOWN_ARROW,
			'left'     : codes.KEY_LEFT_ARROW,
			'right'    : codes.KEY_RIGHT_ARROW,
			'home'     : codes.KEY_HOME,
			'end'      : codes.KEY_END,
			'pageup'   : codes.KEY_PAGE_UP,
			'pagedown' : codes.KEY_PAGE_DOWN,
			'insert'   : codes.KEY_INSERT,
			'u+00007f' : codes.KEY_DELETE,
			'f2'       : codes.KEY_F2
		};
		if (mapping[key]) return mapping[key];
	}
//	return std.browser.isIE ? e.keyCode : std.browser.isOpera ? e.which : e.keyCode;
	return e.keyCode || e.which;
}

std.event.getChar = function (e) {
	var code = std.browser.isIE || std.browser.isOpera ? e.keyCode : e.charCode;
	if (std.browser.isAppleWebKit) {
		if (e.ctrlKey && code < 96) 
			code += 96;
	}
	return String.fromCharCode(code).toUpperCase();
};

std.event.getCharCode = function (e) {
	return std.browser.isIE ? e.keyCode : std.browser.isOpera ? e.which : e.charCode;
};

std.event.listener = function(obj, method) {
	var args = std.array.from(arguments);
	var obj = args.shift();
	var method = args.shift();
	method.prototype; // assert method != null
	if (typeof method != "function") throw "Invalid method: " + method;
	return function(e) {
		return method.apply(obj, [ e || window.event ].concat(args));
	}
}

std.event.element = function(event) {
	return event.target || event.srcElement;
}

std.event.toElement = function (event) {
	return event.relatedTarget || event.toElement;
}

std.event.fromElement = function (event) {
	return event.relatedTarget || event.fromElement;
}

std.event.isLeftClick = function(event) {
	return (((event.which) && (event.which == 1)) || ((event.button) && (event.button == 1)));
}

std.event.isCmdDown = function(event) {
	return event.metaKey && std.browser.platform.isMacOS;
}

std.event.pointerX = function(event) {
	return event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft));
}

std.event.pointerY = function(event) {
	return event.pageY || (event.clientY + (document.documentElement.scrollTop || document.body.scrollTop));
}

std.event.vanish = function(event) {
	std.event.stop(event);
	std.event.preventDefault(event);
}

std.event.stop = function (event) {
	event = event || window.event;
	if (event.stopPropagation)
		event.stopPropagation();
	else
		event.cancelBubble = true;
};

std.event.preventDefault = function (event) {
	event = event || window.event;
	if (event.preventDefault)
		event.preventDefault();
	else
		event.returnValue = false;
};

std.event.observers = false;

std.event._observeAndCache = function(element, name, observer, useCapture) {
	if (!this.observers) this.observers = [];
	if (element.addEventListener) {
		this.observers.push([element, name, observer, useCapture]);
		element.addEventListener(name, observer, useCapture);
	} else if (element.attachEvent) {
		this.observers.push([element, name, observer, useCapture]);
		element.attachEvent('on' + name, observer);
	}
}

std.event.unloadCache = function() {
	if (!std.event.observers) return;
	for (var i = 0; i < std.event.observers.length; i++) {
		std.event.stopObserving.apply(this, std.event.observers[i]);
		std.event.observers[i][0] = null;
	}
	std.event.observers = false;
}

std.event.nativeObserve = function(element, name, observer, useCapture) {

	element = std.dom.element(element);
	element.tagName; // assertion

	useCapture = useCapture || false;

	std.event._observeAndCache(element, name, observer, useCapture);
}

std.event.nativeStopObserving = function(element, name, observer, useCapture) {

	element = std.dom.element(element);
	useCapture = useCapture || false;

	if (element.removeEventListener) {
		element.removeEventListener(name, observer, useCapture);
	} else if (element.detachEvent) {
		element.detachEvent('on' + name, observer);
	}
}

std.event.observe = function(element, name, observer, useCapture) {

	if (name == 'keypress' && (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || document.attachEvent)) {
		name = 'keydown';
	}

	std.event.nativeObserve(element, name, observer, useCapture);
}

std.event.stopObserving = function(element, name, observer, useCapture) {

	if (name == 'keypress' && (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || document.detachEvent)) {
		name = 'keydown';
	}

	std.event.nativeStopObserving(element, name, observer, useCapture);
}

// browser independent key observing, for keydown/keypress/keyup/keyinput
std.event.keyObserve = function(element, name, observer, useCapture) {

	var keyObserver = std.event._keyListener(observer, name);

	switch (name) {
		
		case 'keydown':
		case 'keyup':
			
			std.event.nativeObserve(element, name, keyObserver, useCapture);
			break;

		case 'keypress':
			
			if (std.browser.isIE) {
				std.event.nativeObserve(element, 'keydown', keyObserver, useCapture);
			} else if (std.browser.isAppleWebKit && std.browser.platform.isMacOS) {
				if (std.browser.major == 522) std.event.nativeObserve(element, 'keydown', keyObserver, useCapture);
				std.event.nativeObserve(element, 'keypress', keyObserver, useCapture);
			} else if (std.browser.isAppleWebKit) {
				std.event.nativeObserve(element, 'keydown', keyObserver, useCapture);
			} else {
				std.event.nativeObserve(element, 'keypress', keyObserver, useCapture);
			}
			break;

		case 'keyinput':

			if (std.browser.isAppleWebKit && std.browser.platform.isMacOS) {
				if (std.browser.major == 522) std.event.nativeObserve(element, 'keydown', keyObserver, useCapture);
				std.event.nativeObserve(element, 'keypress', keyObserver, useCapture);
			} else if (std.browser.isAppleWebKit) {
				std.event.nativeObserve(element, 'keypress', keyObserver, useCapture);
			} else {
				std.event.nativeObserve(element, 'keypress', keyObserver, useCapture);
			}
			break;

		default:
			throw 'Unsupported event: ' + name;

	}

}

std.event._keyListener = function(observer, name) {
	return function(e) {

		var ke = { keyIdentifier: null, evt: e || window.event };

		switch (name) {

			case 'keydown':
			case 'keyup':

				if (std.browser.isFF) {

					if (ke.evt.keyCode == 59) { // ";"
						ke.keyIdentifier = 186;
					} else {
						ke.keyIdentifier = ke.evt.keyCode;
					}

				} else if (std.browser.isOpera) {

					if (ke.evt.keyCode == 44) { // ","
						ke.keyIdentifier = 188;
					} else if (ke.evt.keyCode == 47) { // "/"
						ke.keyIdentifier = 191;
					} else if (ke.evt.keyCode == 59) { // ";"
						ke.keyIdentifier = 186;
					} else if (ke.evt.keyCode == 96) { // "`"
						ke.keyIdentifier = 192;
					} else {
						ke.keyIdentifier = ke.evt.keyCode;
					}

				} else {

					ke.keyIdentifier = ke.evt.keyCode;

				}

				break;

			case 'keypress':

				if (std.browser.isFF) {

					if (ke.evt.keyCode) {
						ke.keyIdentifier = ke.evt.keyCode;
					} else if (ke.evt.charCode == 59) { // ";"
						ke.keyIdentifier = 186;
					} else if (ke.evt.charCode == 32 || (ke.evt.charCode >= 65 && ke.evt.charCode <= 90) || (ke.evt.charCode >= 48 && ke.evt.charCode <= 57)) { // Space, A-Z, 0-9
						ke.keyIdentifier = ke.evt.charCode;
					} else if (ke.evt.charCode >= 97 && ke.evt.charCode <= 122) { // a-z
						ke.keyIdentifier = ke.evt.charCode - 32;
					} else {
						ke.keyIdentifier = 0;
					}

				} else if (std.browser.isAppleWebKit) {

					if (ke.evt.type == 'keydown') {
						ke.keyIdentifier = ke.evt.keyCode;
					} else if (ke.evt.charCode == 32 || (ke.evt.charCode >= 65 && ke.evt.charCode <= 90) || (ke.evt.charCode >= 48 && ke.evt.charCode <= 57)) { // Space, A-Z, 0-9
						ke.keyIdentifier = ke.evt.charCode;
					} else if (ke.evt.charCode >= 97 && ke.evt.charCode <= 122) { // a-z
						ke.keyIdentifier = ke.evt.charCode - 32;
					} else {
						ke.keyIdentifier = std.event.KeyMap.AppleWebKit[ke.evt.charCode] || 0;
					}

				} else if (std.browser.isOpera) {

					if (!ke.evt.which) {
						ke.keyIdentifier = ke.evt.keyCode;
					} else if (ke.evt.keyCode == 59) { // ";"
						ke.keyIdentifier = 186;
					} else if (ke.evt.keyCode == 32 || (ke.evt.keyCode >= 65 && ke.evt.keyCode <= 90) || (ke.evt.keyCode >= 48 && ke.evt.keyCode <= 57)) { // Space, A-Z, 0-9
						ke.keyIdentifier = ke.evt.keyCode;
					} else if (ke.evt.keyCode >= 97 && ke.evt.keyCode <= 122) { // a-z
						ke.keyIdentifier = ke.evt.keyCode - 32;
					} else {
						ke.keyIdentifier = ke.evt.keyCode;
					}

				} else {

					ke.keyIdentifier = ke.evt.keyCode;

				}

				break;

			case 'keyinput':

				if (std.browser.isIE) {

					ke.keyIdentifier = ke.evt.keyCode;

				} else if (std.browser.isOpera) {

					ke.keyIdentifier = ke.evt.keyCode == ke.evt.which ? ke.evt.keyCode : 0;

				} else {
					
					ke.keyIdentifier = ke.evt.charCode;

				}

				break;

			default:
				throw 'Illegal event: ' + name;

		}

		return observer(ke);

	}
}

// cross-browser context menu event observe
std.event.onContextMenu = function(element, observer, useCapture) {
		var wrapper = std.event.listener(null, function(e) {
			if (e.button == 0 && e.ctrlKey) observer(e);
		});
		std.event.observe(element, "mousedown", wrapper, useCapture);
		std.event.observe(element, "contextmenu", observer, useCapture);
}

std.event.getEventTarget = function(event) {
	return event.target || event.srcElement;
}

std.event.afterLoaded = function(func) {
	std.event.observe(window, 'load', func, false);
}

/* prevent memory leaks in IE */
std.event.observe(window, 'unload', std.event.unloadCache, false);

////////////////////////////////////////////////////////////////////////////////
// A custom focus manager

std.customfocus = {};

std.customfocus.Manager = function() {
	this._focusList = { next: null };

	this._docFocusListenerList = [];
	this._docBlurListenerList = [];
	std.event.observe(document, 'mousedown', std.event.listener(this, this._onDocMouseDown));
}

std.customfocus.Manager.prototype.newDelegator = function(relative, onfocus, onblur) {
	return new std.customfocus.Delegator(this, relative, onfocus, onblur);
}

std.customfocus.Manager.prototype.focus = function(obj, onfocus, onblur) {
	this.detach(obj);
	var curFocus = this._focusList.next;
	var newFocus = {
		obj: obj,
		onfocus: onfocus,
		onblur: onblur,
		next: this._focusList.next
	};
	this._focusList.next = newFocus;
	if (curFocus && curFocus.onblur) curFocus.onblur.call();
	if (newFocus.onfocus) newFocus.onfocus.call();
}

std.customfocus.Manager.prototype.blur = function(obj) {
	if (this.isFocused(obj)) {
		var curFocus = this._focusList.next;
		var newFocus = this._focusList.next.next;
		this._focusList.next = newFocus;
		if (curFocus.onblur) curFocus.onblur.call();
		if (newFocus && newFocus.onfocus) newFocus.onfocus.call();
	}
}

std.customfocus.Manager.prototype.detach = function(obj) {
	for (var edge = this._focusList; edge.next; edge = edge.next) {
		if (edge.next.obj == obj) {
			edge.next = edge.next.next;
			return;
		}
	}
}

/**
 * Obtain focus for an object as a result of DOM event.
 * This function takes into account that a DOM event can be propagated up the
 * DOM tree, which is not an ideal focus trigger. An ideal focus trigger should
 * only be fired on the focus target, and since then a propagating focus event
 * is generated.
 */
std.customfocus.Manager.prototype.focusOnEvent = function(obj, onfocus, onblur) {
	if (!this._inEvent) {
		this._inEvent = true;
		setTimeout(std.lang.bind(this, this._afterEvent), 0);
		this.focus(obj, onfocus, onblur);
	}
}

std.customfocus.Manager.prototype._afterEvent = function() {
	this._inEvent = false;
}

std.customfocus.Manager.prototype.isFocused = function(obj) {
	return this._focusList.next && this._focusList.next.obj == obj;
}

std.customfocus.Manager.prototype.attachDocFocusListener = function(listener) {
	this._docFocusListenerList.push(listener);
}

std.customfocus.Manager.prototype.attachDocBlurListener = function(listener) {
	this._docBlurListenerList.push(listener);
}

std.customfocus.Manager.prototype.detachDocFocusListener = function(listener) {
	for (var i = 0; i < this._docFocusListenerList.length; i++) {
		if (this._docFocusListenerList[i] == listener) {
			this._docFocusListenerList.splice(i, 1);
			break;
		}
	}
}

std.customfocus.Manager.prototype.detachDocBlurListener = function(listener) {
	for (var i = 0; i < this._docBlurListenerList.length; i++) {
		if (this._docBlurListenerList[i] == listener) {
			this._docBlurListenerList.splice(i, 1);
			break;
		}
	}
}

std.customfocus.Manager.prototype._onDocMouseDown = function(e) {
	this.focusOnEvent(document, std.lang.bind(this, this._onDocFocus), std.lang.bind(this, this._onDocBlur));
}

std.customfocus.Manager.prototype._onDocFocus = function() {
	std.list.iterate(this._docFocusListenerList, function(listener) { listener.call() });
}

std.customfocus.Manager.prototype._onDocBlur = function() {
	std.list.iterate(this._docBlurListenerList, function(listener) { listener.call() });
}

std.customfocus.getManager = function() {
	return std.customfocus._manager || (std.customfocus._manager = new std.customfocus.Manager());
}

std.customfocus.Delegator = function(manager, relative, onfocus, onblur) {
	this._manager = manager;
	this._relative = relative;
	this._onfocus = onfocus;
	this._onblur = onblur;
}

std.customfocus.Delegator.prototype.focus = function() {
	this._manager.focus(this, this._onfocus, this._onblur);
}

std.customfocus.Delegator.prototype.focusOnEvent = function() {
	this._manager.focusOnEvent(this, this._onfocus, this._onblur);
}

std.customfocus.Delegator.prototype.blur = function() {
	this._manager.blur(this);
}

std.customfocus.Delegator.prototype.isFocused = function() {
	return this._manager.isFocused(this);
}

std.customfocus.Delegator.prototype.detach = function() {
	this._manager.detach(this);
}

////////////////////////////////////////////////////////////////////////////////
// Non-blocking Window Resize Listener

std.viewport = {};

std.viewport._resizeListeners = [];
std.viewport._resizeSeq = 0;
std.viewport._resizeTimeout = 40;

// listener = function(bodyWidth, bodyHeight);
std.viewport.addResizeListener = function(listener) {
	if (!std.viewport._initialized) {
		// lazy initialization
		std.event.observe(window, 'resize', std.viewport._onResizeEvent);
		std.event.observe(window, 'load', std.viewport._onResizeEvent);
		std.viewport._initialized = true;

		// iPhone Safari bug: orientation change doesn't always fire onresize
		if (std.browser.isSafariMobile) {
			setInterval(function() {
				if (std.viewport._innerWidth  == window.innerWidth &&
					std.viewport._innerHeight == window.innerHeight)
					return;
				std.viewport._onResizeEvent();
			}, 300);
		}
	}
	std.viewport._resizeListeners.push(listener);
}

std.viewport._lastWidth  = 0;
std.viewport._lastHeight = 0;

std.viewport.fireResize = function() {
	var clz = std.viewport;
	var dim = std.dom.getBodyExtent();
	for (var i = 0; i < std.viewport._resizeListeners.length; i++) {
		std.viewport._resizeListeners[i](dim.width, dim.height, dim.width - clz._lastWidth, dim.height - clz._lastHeight);
	}
	clz._lastWidth  = dim.width;
	clz._lastHeight = dim.height;
}

std.viewport._onResizeEvent = function() {
	
	std.viewport._innerWidth  = window.innerWidth;
	std.viewport._innerHeight = window.innerHeight;
	std.viewport._resizeSeq++;

	if (std.viewport._resizeListeners.length) {
		var seq = std.viewport._resizeSeq;
		setTimeout(function() {
			if (seq == std.viewport._resizeSeq) {
				std.viewport.fireResize();
			}
		}, std.viewport._resizeTimeout);
	}
}

std.viewport.enclose = function(x1, y1, x2, y2) {
	if (typeof(x1) != 'number') {
		var bounds = std.dom.getBounds(std.dom.element(x1));
		x1 = bounds.left;
		y1 = bounds.top;
		x2 = bounds.right;
		y2 = bounds.bottom;
	}
	var dim = std.dom.getBodyExtent();
	return std.math.compareQuad([0, 0], [dim.width, dim.height], [x1, y1], [x2, y2]).enclose == 1;
}

////////////////////////////////////////////////////////////////////////////////
// Channel, a.k.a. custom events

// deprecated
std.channel = {};

std.channel.Channel = function() {
	this.subscribers = [];
}

std.channel.Channel.prototype.subscribe = function(subscriber) {
	this.subscribers.push(subscriber);
}

std.channel.Channel.prototype.unsubscribe = function(subscriber) {
	var list = [];
	while (this.subscribers.length > 0) {
		var element = this.subscribers.shift();
		if (element != subscriber) list.push(element);
	}
	this.subscribers = list;
}

std.channel.Channel.prototype.publish = function(evt) {
	for (var i = 0; i < this.subscribers.length; i++)
		this.subscribers[i](evt);
}

////////////////////////////////////////////////////////////////////////////////
// Topic, aka custom events

std.topic = {};

std.topic.Impl = function(src) {
	this.src = src;
	this.subscribers = [];
}

std.topic.Impl.prototype.subscribe = function(func) {
	this.subscribers.push(func);
}

std.topic.Impl.prototype.unsubscribe = function(func) {
	this.subscribers = std.array.without(this.subscribers, func);
}

std.topic.Impl.prototype.publish = function() {
	this._publishWithArgs(std.array.from(arguments));
}

std.topic.Impl.prototype._publishWithArgs = function(args) {
	for (var i = 0; i < this.subscribers.length; i++) {
		this.subscribers[i].apply(this.src, args);
	}
}

std.topic.define = function(obj, name) {
	obj[name] = new std.topic.Impl(obj);
}

std.topic.subscribe = function(obj, name, func) {
	obj[name].subscribe(func);
}

std.topic.unsubscribe = function(obj, name, func) {
	obj[name].unsubscribe(func);
}

std.topic.publish = function(obj, name) {
	obj[name]._publishWithArgs(std.array.from(arguments).slice(2));
}

////////////////////////////////////////////////////////////////////////////////
// CSS Manipulation

std.css = {};

std.css.bulkset = function() { // bulkset(name, [name, ele1, ele2, ...], [name, ele1, ele2, ...])
	var attr = arguments[0];
	for (var i = 1; i < arguments.length; i++) {
		var data = arguments[i];
		var value = data[0];
		for (var j = 1; j < data.length; j++)
			std.dom.element(data[j]).style[attr] = value;
	}
}



std.css.getStyle = function(element, style) {
	std.css.getElementStyle(std.dom.element(element), style);
}

std.css.getElementStyle = function(element, style) {
	var value = element.style[std.string.camelize(style)];
	if (!value) {
		if (document.defaultView && document.defaultView.getComputedStyle) {
			var css = document.defaultView.getComputedStyle(element, null);
			value = css ? css.getPropertyValue(style) : null;
		} else if (element.currentStyle) {
			value = element.currentStyle[std.string.camelize(style)];
		}
	}

	if (window.opera && (style == 'left' || style == 'top' || style == 'right' || style == 'bottom')) {
		if (std.css.getStyle(element, 'position') == 'static') value = 'auto';
	}
	return value == 'auto' ? null : value;
}

std.css.setStyle = function(element, style) {
	element = std.dom.element(element);
	for (name in style)
		element.style[std.string.camelize(name)] = style[name];
}

std.css.includeClass = function(element, className) {
	var classNames = element.className.split(" ");
	var found = 0;
	for (var i = 0; i < classNames.length; i++) {
		if (classNames[i] == className) {
			found = 1;
			break;
		}
	}
	if (!found) {
		classNames.push(className);
		element.className = classNames.join(" ");
	}
}

std.css.excludeClass = function(element, className) {
	var srcClassNames = element.className.split(" ");
	var dstClassNames = [];
	for (var i = 0; i < srcClassNames.length; i++) {
		if (srcClassNames[i] != className) dstClassNames.push(srcClassNames[i]);
	}
	if (srcClassNames.length != dstClassNames.length) element.className = dstClassNames.join(" ");
}

std.css._switchOnEvent = function(element, stateAttribute, className, undoEvent) {
	std.css.includeClass(element, className);
	if (!element[stateAttribute]) {
		if (undoEvent)
			std.event.observe(element, undoEvent, std.lang.bind(null, std.css.excludeClass, element, className));
		element[stateAttribute] = true;
	}
}

std.css.hoverClass = function(element, className) {
	std.css._switchOnEvent(element, 'std.css.hoverClass.set', className, 'mouseout');
}

std.css.clickClass = function(element, className) {
	std.css._switchOnEvent(element, 'std.css.clickClass.set', className, 'mouseup');
}

std.css.focusClass = function(element, className) {
	std.css._switchOnEvent(element, 'std.css.focusClass.set', className, 'blur');
}

////////////////////////////////////////////////////////////////////////////////
// Form Manipulation (from prototype.js)

std.form = {};

std.form.submit = function(form) {
	if (typeof form.onsubmit == 'function') {
		if (form.onsubmit()) form.submit();
	} else {
		form.submit();
	}
}

std.form.serialize = function(form) {
	var elements = std.form.getElements(std.dom.element(form));
	var queryComponents = new Array();

	for (var i = 0; i < elements.length; i++) {
		var queryComponent = std.form.element.serialize(elements[i]);
		if (queryComponent)	queryComponents.push(queryComponent);
	}

	return queryComponents.join('&');
}

std.form.getElements = function(form) {
	form = std.dom.element(form);
	var elements = new Array();

	for (tagName in std.form.element.serializer) {
		var tagElements = form.getElementsByTagName(tagName);
		for (var j = 0; j < tagElements.length; j++) elements.push(tagElements[j]);
	}
	return elements;
}

std.form.getInputs = function(form, typeName, name) {
	form = std.dom.element(form);
	var inputs = form.getElementsByTagName('input');

	if (!typeName && !name)	return inputs;

	var matchingInputs = new Array();
	for (var i = 0; i < inputs.length; i++) {
		var input = inputs[i];
		if ((typeName && input.type != typeName) ||	(name && input.name != name)) continue;
		matchingInputs.push(input);
	}

	return matchingInputs;
}

std.form.calculateChecked = function(form, name) {
	var checkboxes = std.form.getInputs(form, 'checkbox', name);
	var c = 0;
	for (var i = 0; i < checkboxes.length; i++) {
		if (checkboxes[i].checked) c++;
	}
	return c;
}

std.form.checkboxSync = function(master, name) {
	std.list.iterate(std.form.getInputs(master.form, 'checkbox', name), function(slave) {
		slave.checked = master.checked;
	});
}

std.form.value = function(form, name) {
	return std.form.element.getValue(form.elements.namedItem(name));
}

std.form.disable = function(form) {
	var elements = std.form.getElements(form);
	for (var i = 0; i < elements.length; i++) {
		var element = elements[i];
		element.blur();
		element.disabled = 'true';
	}
}

std.form.enable = function(form) {
	var elements = std.form.getElements(form);
	for (var i = 0; i < elements.length; i++) {
		var element = elements[i];
		element.disabled = '';
	}
}

std.form.findFirstElement = function(node) {
	var formlist = node.tagName.toLowerCase() == 'form' ? [ node ] : node.getElementsByTagName('form');
	for (var i = 0; i < formlist.length; i++) {
		var elements = std.form.getElements(formlist[i]);
		for (var j = 0; j < elements.length; j++) {
			var tagName = elements[j].tagName.toLowerCase();
			if (elements[j].type != 'hidden' && !elements[j].disabled && ('input' == tagName || 'select' == tagName || 'textarea' == tagName))
				return elements[j];
		}
	}
	return null;
}

std.form.isIntrinsicControl = function(node) {
	return '|BUTTON|INPUT|TEXTAREA|SELECT|OPTION|A|'.indexOf(node.tagName) != -1;
}

std.form.focusFirstElement = function(node) {
	var element = std.form.findFirstElement(node);
	if (element) {
		element.focus();
		if (element.select) element.select();
		return true;
	} else {
		return false;
	}
}

std.form.focusDocumentFirstElement = function() {
	var forms = document.forms;
	for (var i = 0; i < forms.length; i++) {
		if (std.form.focusFirstElement(forms[i])) break;
	}	
}

std.form.reset = function(form) {
	std.dom.element(form).reset();
}

std.form.element = {};

std.form.element.serialize = function(element) {
	element = std.dom.element(element);
	var method = element.tagName.toLowerCase();
	var parameter = std.form.element.serializer[method](element);

	if (parameter) {
		var key = encodeURIComponent(parameter[0]);
		if (key.length == 0) return;

		if (parameter[1].constructor != Array) parameter[1] = [parameter[1]];

		return std.list.map(parameter[1], function(value) {
			return key + '=' + encodeURIComponent(value);
		}).join('&');
	}
}

std.form.element.getValue = function(element) {
	element = std.dom.element(element);
	var method = element.tagName.toLowerCase();
	var parameter = std.form.element.serializer[method](element);

	if (parameter) return parameter[1];
}

std.form.element.serializer = {};

std.form.element.serializer.input = function(element) {
	switch (element.type.toLowerCase()) {
		case 'submit':
		case 'hidden':
		case 'password':
		case 'text':
			return std.form.element.serializer.textarea(element);
		case 'checkbox':
		case 'radio':
			return std.form.element.serializer.inputSelector(element);
	}
	return false;
}

std.form.element.serializer.inputSelector = function(element) {
	if (element.checked) return [element.name, element.value];
}

std.form.element.serializer.textarea = function(element) {
	return [element.name, element.value];
}

std.form.element.serializer.select = function(element) {
	return std.form.element.serializer[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element);
}

std.form.element.serializer.selectOne = function(element) {
	var value = '', opt, index = element.selectedIndex;
	if (index >= 0) {
		opt = element.options[index];
		value = opt.value;
		if (!value && !('value' in opt)) value = opt.text;
	}
	return [element.name, value];
}

std.form.element.serializer.selectMany = function(element) {
	var value = new Array();
	for (var i = 0; i < element.length; i++) {
		var opt = element.options[i];
		if (opt.selected) {
			var optValue = opt.value;
			if (!optValue && !('value' in opt)) optValue = opt.text;
			value.push(optValue);
		}
	}
	return [element.name, value];
}

std.form._ifEmptyDisableHandler = function(input, buttons) {
	for (var i=0; i<buttons.length; ++i) {
		(function() { buttons[i].disabled = input.value == '' })();
	}
}

std.form.ifEmptyDisable = function(input, buttons) {
	std.event.observe(input, 'keyup', function() {
		setTimeout(function() { std.form._ifEmptyDisableHandler(input, buttons) }, 1);
	});
	std.form._ifEmptyDisableHandler(input, buttons);
}


////////////////////////////////////////////////////////////////////////////////
// Generic AJAX Framework (from prototype.js)

std.ajax = {};

std.ajax._processorCount = 0;
std.ajax._jsonpProcessors = {};

std.ajax.obtainProcessorId = function () {
	if (std.ajax._processorCount > 100000) std.ajax._processorCount = 0;
	return std.ajax._processorCount++;
};

std.ajax.registerJsonpProcessor = function (id, processor) {
	std.ajax._jsonpProcessors[id] = processor;
};

std.ajax.removeJsonpProcessor = function (id) {
	delete std.ajax._jsonpProcessors[id];
};

std.ajax.jsonp = function (jsonpid, response, status, headers) {
	if (typeof status == "undefined") status = 200;
	if (typeof headers == "undefined") headers = "";
	std.ajax._jsonpProcessors[jsonpid](status, headers, response);
};

std.ajax.ScriptTagRequest = function () {
	this._jsonpId = std.ajax.obtainProcessorId();
	this._tag = null;
	this._url = null;
	this._content = null;
	this._responseSuccess = false;
	this._processError = false;
	this.onreadystatechange = null;
	this.readystate = 0;
	this.responseText = "";
	this.status = 0;
	this.statusText = "";
	this._headers = null;
	this._maxLength = std.ajax.ScriptTagRequest.MAX_LENGTH;
};

std.ajax.ScriptTagRequest.MAX_LENGTH = 1800;
std.ajax.ScriptTagRequest._idPrefix = Math.random() + "." + new Date().getTime();

std.ajax.ScriptTagRequest.prototype._reset = function () {
	this._removeScriptTag();
	this._content = null;
	this._responseSuccess = false;
	this._processError = false;
	this.readyState = 0;
	this.responseText = "";
	this.status = 0;
	this.statusText = "";
	this._headers = null;
	this._maxLength = std.ajax.ScriptTagRequest.MAX_LENGTH;
};

std.ajax.ScriptTagRequest.prototype._removeScriptTag = function () {
	if (this._tag) document.getElementsByTagName("HEAD")[0].removeChild(this._tag);
	this._tag = null;
	if (this._jsonpId) std.ajax.removeJsonpProcessor(this._jsonpId);
};

std.ajax.ScriptTagRequest.prototype._createScriptTag = function (content, first, last) {
	this._removeScriptTag();

	this._responseSuccess = false;
	this._processError = false;

	std.ajax.registerJsonpProcessor(this._jsonpId, std.lang.bind(this, this._processResponse));
	
	this._tag = document.createElement("script");
	this._tag.type = "text/javascript";
	var url = this._url + (this._url.indexOf("?") == -1 ? "?" : "&") + "i=" + std.ajax.ScriptTagRequest._idPrefix;
	url += "&jsonp=" + this._jsonpId;
	url += "&c=" + content;
	if (first) url += "&f=1";
	if (last) url += "&l=1";
	this._tag.src = url;

	std.event.observe(this._tag, "load", std.event.listener(this, this._onload));
	std.event.observe(this._tag, "error", std.event.listener(this, this._onerror));
	std.event.observe(this._tag, "readystatechange", std.event.listener(this, this._onreadystatechange));

	document.getElementsByTagName("HEAD")[0].appendChild(this._tag);
};

std.ajax.ScriptTagRequest.prototype._onload = function () {
	this._removeScriptTag();
	if (!this._responseSuccess) {
		this._onerror();	
	} else {
		this._sendContent();
	}
};

std.ajax.ScriptTagRequest.prototype._onerror = function () {
	this._removeScriptTag();
	if (!this._processError) {
		this._processError = true;
		this.status = 500;
		this.statusText = "Error";
		this.readyState = 4;
		this.responseText = "";
		if (this.onreadystatechange && typeof this.onreadystatechange == 'function') this.onreadystatechange();
	}
};

std.ajax.ScriptTagRequest.prototype._onreadystatechange = function () {
	if (this._tag && this._tag.readyState && this._tag.readyState == "loaded")
	{
		this._onload();
	}
};

std.ajax.ScriptTagRequest.prototype._sendContent = function (content) {
	if (content == null && this._content == null) return;
	var first = (content != null);
	var last;
	if (first) this._content = content;
	var c;
	if (this._content.length > this._maxLength) {
		var length = this._getMaxLength();
		c = this._content.substring(0, length);
		this._content = this._content.substring(length);
		last = false;
	} else {
		c = this._content;
		this._content = null;
		last = true;
	}
	this._createScriptTag(c, first, last);
};

std.ajax.ScriptTagRequest.prototype._getMaxLength = function () {
	var length = this._maxLength;
	var content = this._content;
	while (true) {
		if (content.charAt(length - 1) == '%') {
			length -= 1;
		} else if (content.charAt(length - 2) == '%') {
			length -= 2;
		} else if (content.charAt(length - 3) == '%') {
			var higherOctet = std.conv.hexToInt(content.charAt(length - 2));
			if (higherOctet <= 7) break;
			if (higherOctet >= 8 && higherOctet <= 11) length -= 3;
			if (higherOctet >= 12) { length -= 3; break; }
		} else {
			break;
		}
	}
	return length;
};

std.ajax.ScriptTagRequest.prototype._processResponse = function (status, headers, content) {
	this._responseSuccess = true;
	if (status != -1)	{
		this.status = status;
		this._headers = headers;
		this.responseText = content;
		this.readyState = 4;
		if (this.onreadystatechange && typeof this.onreadystatechange == "function") this.onreadystatechange();
	} 
}

std.ajax.ScriptTagRequest.prototype.open = function (method, url, async, userName, password) {
	this._reset();
	this._url = url;
	this._maxLength -= url.length;
};

std.ajax.ScriptTagRequest.prototype.send = function (content) {
	this._reset();
	content = content == null ? "" : encodeURIComponent(content);
	this._sendContent(content);
};

std.ajax.ScriptTagRequest.prototype.setRequestHeader = function () {
};

std.ajax.ScriptTagRequest.prototype.getAllResponseHeaders = function () {
	return this._headers;
};

std.ajax.ScriptTagRequest.prototype.getResponseHeader = function (key) {
	if (!key) return null;
	var value = null;
	key = key.toLowerCase() + ":";
	var headers = this._headers.toLowerCase();
	var i = -1;
	if ((i = headers.indexOf(key)) != -1) {
		value = this._headers.substring(i + key.length, this._headers.indexOf("\n", i));
		value = std.string.trim(value);
	}
	return value;
};

std.ajax.ScriptTagRequest.prototype.abort = function () {
	this._removeScriptTag();
};

std.ajax.getTransport = function (transportFactory) {
	if (typeof transportFactory == 'string') return eval(transportFactory)();
	return std.ajax.getDefaultTransport();
}

std.ajax.getScriptTagTransport = function() {
	return new std.ajax.ScriptTagRequest();
};

std.ajax.getDefaultTransport = function() {
	try { return new ActiveXObject('Msxml2.XMLHTTP') } catch (e) {}
	try { return new ActiveXObject('Microsoft.XMLHTTP') } catch (e) {}
	try { return new XMLHttpRequest() } catch (e) {}
	return false;
};

std.ajax._activeRequestCount = 0;

std.ajax.responders = {}

std.ajax.responders._list = [];

std.ajax.responders._register = function(responder) {
	if (!std.array.contains(this._list, responder)) this._list.push(responder);
}

std.ajax.responders._unregister = function(responder) {
    this._list = std.array.without(this._list, responder);
}

std.ajax.responders._dispatch = function(callback, request, transport, json) {
	std.list.iterate(this._list, function(responder) {
		if (responder[callback] && typeof responder[callback] == 'function') {
			try {
				responder[callback].apply(responder, [request, transport, json]);
			} catch (e) {}
		}
	});
}

std.ajax.responders._register({
	onCreate: function() { std.ajax._activeRequestCount++; },
	onComplete: function() { std.ajax._activeRequestCount--; }
});

std.ajax.Base = function() {}

std.ajax.Base.prototype.setOptions = function(options) {
	this.options = { method: 'post', asynchronous: true, parameters: '' }
	std.lang.merge(this.options, options || {});
}

std.ajax.Base.prototype.responseIsSuccess = function() {
	return this.transport.status == undefined
			|| this.transport.status == 0
			|| (this.transport.status >= 200 && this.transport.status < 300);
}

std.ajax.Base.prototype.responseIsFailure = function() {
	return !this.responseIsSuccess();
}

std.ajax.Request = function(url, options) {
    this.setOptions(options);
    this.transport = std.ajax.getTransport(this.options.transportFactory);
    this.request(url);
}
std.ajax.Request.prototype = new std.ajax.Base();

std.ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

std.ajax.Request.prototype.request = function(url) {
	var parameters = this.options.parameters || '';
	if (parameters.length > 0) parameters += '&_=';

	try {
		this.url = url;
		if (this.options.method == 'get' && parameters.length > 0)
			this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;

		std.ajax.responders._dispatch('onCreate', this, this.transport);

		this.transport.open(this.options.method, this.url,
			this.options.asynchronous);

		if (this.options.asynchronous) {
			this.transport.onreadystatechange = std.lang.bind(this, this.onStateChange);
			setTimeout(std.lang.bind(this, function() { this.respondToReadyState(1) }), 10);
		}

		this.setRequestHeaders();

		var body = this.options.postBody ? this.options.postBody : parameters;
		this.transport.send(this.options.method.toLowerCase() == 'post' || this.options.method.toLowerCase() == 'put'? body : null);

	} catch (e) {
		this.dispatchException(e);
	}
}

std.ajax.Request.prototype.setRequestHeaders = function() {
		var requestHeaders = [];

		if (this.options.method == 'post') {

			requestHeaders.push('Content-type', this.options.contentType ? this.options.contentType : 'application/x-www-form-urlencoded');

			/* Force "Connection: close" for Mozilla browsers to work around
			 * a bug where XMLHttpReqeuest sends an incorrect Content-length
			 * header. See Mozilla Bugzilla #246651.
			 */
			if (this.transport.overrideMimeType)
				requestHeaders.push('Connection', 'close');
		}

		if (this.options.requestHeaders)
			requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);

		for (var i = 0; i < requestHeaders.length; i += 2)
			this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
}

std.ajax.Request.prototype.onStateChange = function() {
		var readyState = this.transport.readyState;
		if (readyState != 1)
			this.respondToReadyState(this.transport.readyState);
}

std.ajax.Request.prototype.header = function(name) {
		try {
			return this.transport.getResponseHeader(name);
		} catch (e) {}
}

std.ajax.Request.prototype.evalJSON = function() {
		try {
			return eval(this.header('X-JSON'));
		} catch (e) {}
}

std.ajax.Request.prototype.evalResponse = function() {
		try {
			return eval(this.transport.responseText);
		} catch (e) {
			this.dispatchException(e);
		}
}

std.ajax.Request.prototype.respondToReadyState = function(readyState) {

		if (typeof(std) == 'undefined') { return } 
		var event = std.ajax.Request.Events[readyState];
		var transport = this.transport, json = this.evalJSON();

		if (event == 'Complete') {
			try {
				(this.options['on' + this.transport.status]
				 || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
				 || std._emptyFunction)(transport, json);
			} catch (e) {
				this.dispatchException(e);
			}

			if ((this.header('Content-type') || '').match(/^text\/javascript/i))
				this.evalResponse();
		}

		try {
			(this.options['on' + event] || std._emptyFunction)(transport, json);
			std.ajax.responders._dispatch('on' + event, this, transport, json);
		} catch (e) {
			this.dispatchException(e);
		}

		/* Avoid memory leak in MSIE: clean up the oncomplete event handler */
		if (event == 'Complete')
			this.transport.onreadystatechange = std._emptyFunction;
}

std.ajax.Request.prototype.dispatchException = function(exception) {
		(this.options.onException || std._emptyFunction)(this, exception);
		std.ajax.responders._dispatch('onException', this, exception);
}

std.ajax.Updater = function(container, url, options) {
		this.containers = {
			success: container.success ? std.dom.element(container.success) : std.dom.element(container),
			failure: container.failure ? std.dom.element(container.failure) :
				(container.success ? null : std.dom.element(container))
		}
		this.transport = std.ajax.getTransport();
		this.setOptions(options);

		var onComplete = this.options.onComplete || std._emptyFunction;
		this.options.onComplete = std.lang.bind(this, function(transport, object) {
			this.updateContent();
			onComplete(transport, object);
		});

		this.request(url);
}

std.ajax.Updater.prototype = new std.ajax.Request();

std.ajax.Updater.prototype.updateContent = function() {
    var receiver = this.responseIsSuccess() ? this.containers.success : this.containers.failure;
    var response = this.transport.responseText;

    if (!this.options.evalScripts)
		response = std.string.stripScripts(response);

    if (receiver) {
		if (this.options.insertion) {
			new this.options.insertion(receiver, response);
		} else {
			std.dom.update(receiver, response);
		}
	}

	if (this.responseIsSuccess()) {
		if (this.onComplete)
			setTimeout(std.lang.bind(this, this.onComplete), 10);
	}
}

std.ajax.getText = function(url) {
	var ajax = new std.ajax.Request(url, { method: 'post', asynchronous: false });
	return ajax.transport.responseText;
}

std.ajax.newTree = function(url) {
	var text = std.ajax.getText(url);
	return std.dom.newTree(text);
}

std.ajax.ping = function(url, options) {
	new std.ajax.Request(url, std.hash.merge(options, { method: 'post', asynchronous: true }));
}

std.ajax.loadMap = function(map, onSuccess, options) {
	var len = 0;
	var dst = {};
	for (var i in map) { len++ }
	for (var i in map) {
		(function(idx) {
			new std.ajax.Request(map[i], std.hash.merge(options, { onSuccess: function(response) {
				dst[idx] = { responseText: response.responseText }
				if (!--len) { onSuccess(dst) }
			}}));
		})(i);
	}
}

////////////////////////////////////////////////////////////////////////////////
// Mocker for auto ajax pages

std.mocker = {
	instances: new Array(),
	regExpNodes: {},
	extractNodeContent: function (tagName, html) {
	    var re, res;
	    if (!(re = std.mocker.regExpNodes[tagName]))
		std.mocker.regExpNodes[tagName] = re = new RegExp('<' + tagName + '[^>]*>', 'im');
	    res = re.exec(html);
	    return res ? html.substring(res.index + res[0].length, html.indexOf('</' + tagName + '>')) : '';
	},
    release: function () {
		for (var k in std.mocker.instances)
			std.mocker.instances[k].release();
	}
}

/*
   	target:      target element
	url:         url for initial content
	initialize:  fill the target element with content immediately
*/
std.mocker.mock = function(target, url, initialize, onload, transportFactory, extraUrlParams) {
	var mockInstance = new std.mocker.MockInstance(target, url, onload, transportFactory, extraUrlParams);
	if (initialize) window.setTimeout(std.lang.bind(mockInstance, mockInstance.load), 0);
	return mockInstance;
}

std.mocker.MockInstance = function(target, initUrl, onload, transportFactory, extraUrlParams) {
	this.target = std.dom.element(target);
	this.initUrl = initUrl;
	this.instanceId = std.mocker.instances.length;
	this.onload = onload;
	this.transportFactory = transportFactory;
	this.extraUrlParams = extraUrlParams || {};
	std.mocker.instances.push(this);
}

std.mocker.MockInstance.prototype.load = function() {
	this.loaded = false;
	var ajax = new std.ajax.Request(this._constructUrl(this.initUrl), {
		transportFactory: this.transportFactory,
		method: 'get',
		asynchronous: true,
		onSuccess: std.lang.bind(this, this._transfer)
	});
}

std.mocker.MockInstance.prototype._constructUrl = function (url) {
	for (var key in this.extraUrlParams) {
		url += (url.indexOf('?') == -1 ? '?' : '&') + encodeURIComponent(key) + "=" + encodeURIComponent(this.extraUrlParams[key]);
	}
	return url;
};

std.mocker.MockInstance.prototype._handleSubmit = function(form) {
	var ajax = new std.ajax.Request(this._constructUrl(form.action), {
		transportFactory: this.transportFactory,
		method: 'post',
		asynchronous: true,
		postBody: std.form.serialize(form),	
		onSuccess: std.lang.bind(this, this._transfer)
	});
}

std.mocker.MockInstance.prototype._handleLink = function(link) {
	var ajax = new std.ajax.Request(this._constructUrl(link.href), {
		transportFactory: this.transportFactory,
		method: 'get',
		asynchronous: true,
		onSuccess: std.lang.bind(this, this._transfer)
	});
}

std.mocker.MockInstance.prototype._transfer = function(req) {
	var self = this;
	var html = req.responseText;
	var bodyHtml = std.mocker.extractNodeContent('body', html);
	this.target.innerHTML = std.string.stripScripts(bodyHtml);

	std.list.iterate(this.target.getElementsByTagName('a'), function(link, idx) {
		if (link.getAttribute('preserve')) return;
		if (!link.href.indexOf('javascript:')) return;
		var cascade = link.onclick;
		link.onclick = function(e) {
			try {
				if (cascade) cascade.call(link, e);
				self._handleLink(link);
			} catch (ex) {
				alert("Exception executing Mocker link: " + ex);
			}
			return false;
		}
	});

	std.list.iterate(this.target.getElementsByTagName('form'), function(form, idx) {
		if (form.getAttribute('preserve')) return;
		if (!form.action.indexOf('javascript:')) return;
		var cascade = form.onsubmit;
		form.onsubmit = function(e) {
			try {
				var ret;
				if (cascade) { ret = cascade.call(form, e) }
				if (typeof(ret) == 'undefined' || ret) { self._handleSubmit(form) }
			} catch (ex) {
				alert("Exception executing Mocker submit: " + ex);
			}
			return false;
		}
	});
	try { eval(std.mocker.extractNodeContent('script', bodyHtml)) } catch (e) {
		std.log.warn(e);
	}
	if (!this.loaded) {
		this.loaded = true;
		if (this.onload) { this.onload() }
	}
}

std.mocker.MockInstance.prototype.release = function(req) {
	std.list.iterate(this.target.getElementsByTagName('a'), function (link, idx) { link.onclick = null; link.innerHTML = ''; });
	std.list.iterate(this.target.getElementsByTagName('form'), function (form, idx) { form.onsubmit = null; form.innerHTML = ''; });
	this.target.innerHTML = '';
	this.target = null;
}

////////////////////////////////////////////////////////////////////////////////
// Updater using AJAX Form/Link Handling

std.updater = {};

std.updater._beginUpdate = function(stash) {
	stash.src._std_updater_updating = true;
	if (stash.busyElementId) std.dom.element(stash.busyElementId).style.display = "";
	if (stash.busyDisableForm) std.form.disable(stash.src);
	if (stash.preUpdate && stash.preUpdate != "") setTimeout(function() { eval(stash.preUpdate); }, 0);
}

std.updater._endUpdate = function(stash) {
	stash.src._std_updater_updating = null;
	if (stash.busyElementId) std.dom.element(stash.busyElementId).style.display = "none";
	if (stash.busyDisableForm) std.form.enable(stash.src);
	if (stash.postUpdate && stash.postUpdate != "") setTimeout(function() { eval(stash.postUpdate); }, 0);
}

std.updater._responseHandler = function(resp, stash) {
	if (stash.updateElementId) {
		var div = document.createElement("div");
		div.innerHTML = resp.responseText;
		var elements = div.getElementsByTagName("*");
		for (var i = 0; i < elements.length; i++) {
			var id = elements[i].getAttribute("id");
			if (id && id == stash.updateElementId) {
				var dest = document.getElementById(stash.updateElementId);
				if (dest) dest.innerHTML = elements[i].innerHTML;
				break;
			}
		}
	}
}

std.updater.handleForm = function(form) {
	if (form._std_updater_updating) return;
	var url = form.action;
	var params = std.form.serialize(form);
	var busyDisableFormString = form.getAttribute("busyDisableForm");
	var stash = {
		src: form,
		updateElementId: form.getAttribute("updateElement"),
		busyElementId: form.getAttribute("busyElement"),
		busyDisableForm: busyDisableFormString ? "true" == busyDisableFormString.toLowerCase() : false,
		preUpdate: form.getAttribute("preUpdate"),
		postUpdate: form.getAttribute("postUpdate")
	}
	std.updater._beginUpdate(stash);
	var req = new std.ajax.Request(url, {
		method: 'post',
		parameters: params,
		onComplete: function(resp) { std.updater._endUpdate(stash); std.updater._responseHandler(resp, stash); },
		onFailure: function(resp) { alert('ERROR ' + resp.status + ' -- ' + resp.statusText) }
	});
}

std.updater.handleLink = function(link) {
	if (link._std_updater_updating) return;
	var url = link.href;
	var stash = {
		src: link,
		updateElementId: link.getAttribute("updateElement"),
		busyElementId: link.getAttribute("busyElement"),
		preUpdate: link.getAttribute("preUpdate"),
		postUpdate: link.getAttribute("postUpdate")
	}
	std.updater._beginUpdate(stash);
	var req = new std.ajax.Request(url, {
		method: 'get',
		onComplete: function(resp) { std.updater._endUpdate(stash); std.updater._responseHandler(resp, stash); },
		onFailure: function(resp) { alert('ERROR ' + resp.status + ' -- ' + resp.statusText) }
	});
}

////////////////////////////////////////////////////////////////////////////////
// Collects Performance Statistics

std.perf = {};

std.perf.base = new Date();
std.perf.data = {};

std.perf.mark = function(label, replace) {
	if (replace || typeof(this.data[label]) == "undefined")
		this.data[label] = new Date() - std.perf.base;
}

std.perf.list = function() {
	var result = [];
	var last = 0;
	for (var label in this.data) {
		var time = this.data[label];
		result.push(label + ": " + (time - last) + " @" + time + "ms");
		last = time;
	}
	return result;
}

std.perf.csv = function() {
	var labels = [];
	var values = [];
	var last = 0;
	for (var label in this.data) {
		var time = this.data[label];
		labels.push(label);
		values.push(time - last);
		last = time;
	}
	labels.push(values);
	return labels.join(",");
}

std.perf.newElementCount = {};

////////////////////////////////////////////////////////////////////////////////
// Logging facility

std.log = {};

std.log.message = function(level, message, nothrow) {
	if (std.browser.isAppleWebKit) {
		console.log("[" + level + "] " + message);
	} else if (typeof console != "undefined") {
		switch (level) {
			case "DEBUG": console.debug(message); break;
			case " INFO": console.info(message); break;
			case " WARN": console.warn(message); break;
			case "ERROR": console.error(message); break;
		}
	} else if (typeof opera != "undefined") {
		opera.postError("[" + level + "] " + message);
	} else if (!nothrow) {
		setTimeout(function() { throw new Error("[" + level + "] " + message) }, 0);
	}
}

std.log.debug = function(message) {
	std.log.message("DEBUG", message);
}

std.log.info = function(message) {
	std.log.message(" INFO", message);
}

std.log.warn = function(message) {
	std.log.message(" WARN", message);
}

std.log.error = function(message) {
	std.log.message("ERROR", message);
}

////////////////////////////////////////////////////////////////////////////////
// Conversion utils

std.conv = {};

std.conv._hexTab = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70];

std.conv.intToRGB = function (num) {
	return String.fromCharCode(
		this._hexTab[(num >> 20) & 0xf],
		this._hexTab[(num >> 16) & 0xf],
		this._hexTab[(num >> 12) & 0xf],
		this._hexTab[(num >> 8) & 0xf],
		this._hexTab[(num >> 4) & 0xf],
		this._hexTab[num & 0xf]);
};

std.conv.hexToInt = function (c) {
    var upperCode = c.toUpperCase().charCodeAt(0);
    if (48 <= upperCode && upperCode <= 58)
	return upperCode - 48;
    if (65 <= upperCode && upperCode <= 70)
	return upperCode - 65 + 10;
    return 0;
};

std.conv.rgbToIntArray = function (s) {
    return [
	16 * std.conv.hexToInt(s.charAt(0)) + std.conv.hexToInt(s.charAt(1)),
	16 * std.conv.hexToInt(s.charAt(2)) + std.conv.hexToInt(s.charAt(3)),
	16 * std.conv.hexToInt(s.charAt(4)) + std.conv.hexToInt(s.charAt(5))]
};

std.conv.rgbToInt = function (s) {
    var arr = std.conv.rgbToIntArray(s);
    return arr[0] * 65536 + arr[1] * 256 + arr[2];
};

////////////////////////////////////////////////////////////////////////////////
// Date Conversion utils

std.date = {};

std.date.fromSqlUTC = function(str) {
	var time = Date.UTC(
		str.substr(0, 4),
		str.substr(4, 2) - 1,
		str.substr(6, 2),
		str.substr(8, 2),
		str.substr(10, 2),
		str.substr(12, 2)
	);
	return new Date(time);
}

std.date.asTimeStrUTC = function(date) {
	return date.toUTCString().split(' ')[4] + (date.getUTCMilliseconds() / 1000).toFixed(3).substring(1);
}

std.date.format = function(dt, fmt) { // this is a stub
	var invoke = { y: 'getFullYear', M: 'getMonth', d: 'getDate', H: 'getHours', m: 'getMinutes', s: 'getSeconds' };
	var result = {};
	var search = ['yyyy', 'yy', 'MM', 'dd', 'HH', 'mm', 'ss'];
	var repeat = [4, 2, 2, 2, 2, 2, 2];
	var offset = [0, 0, 1, 0, 0, 0, 0];
	var HH2hh  = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
	var HH2am  = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

	var months = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
	var val, res;
	while (fmt.indexOf('MMM') != -1) {
		if (val == null)
			val = '' + months[result['M'] = dt.getMonth() + 1];
		fmt = fmt.replace('MMM', val);
	}
	val = null;
	while (fmt.indexOf('hh') != -1) {
		if (val == null)
			val = dt.getHours();
		fmt = fmt.replace('hh', HH2hh[val]);
	}
	while (fmt.indexOf('a') != -1) {
		if (val == null)
			val = dt.getHours();
		fmt = fmt.replace('a', HH2am[val] ? 'AM' : 'PM');
	}
	for (var i=0; i<search.length; ++i) {
		var pat = search[i], val = null;
		while (fmt.indexOf(pat) != -1) {
			if (val == null) {
				var c = pat.charAt(0);
				var r = repeat[i];
				if ((res = result[c]) == null)
					 res = result[c] = '' + ((dt[invoke[c]]() - 0) + offset[i]);
				val = std.string.lpad(std.string.right(res, r), r, '0');
			}
			fmt = fmt.replace(pat, val);
		}
	}
	return fmt;
}

///////////////////////////////////////////////////////////////////////////////
// Cookies Util (src: http://www.quirksmode.org/js/cookies.html)

std.cookies = {};

std.cookies.set = function (name,value,days) {
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path=/";
}

std.cookies.get = function (name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
	var c = ca[i];
	while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	}
	return null;
}

std.cookies.remove = function (name) {
	std.cookies.set (name,"",-1);
}

///////////////////////////////////////////////////////////////////////////////
// Animation

std.ani = {};

std.ani.Interpolator = function() {
	this._onEvent = null;
	this._cp = null;
	this._timer = null;
}

std.ani.Interpolator.prototype.restart = function(sp, ep, period, step) {
	this.reset(sp);
	this.start(sp, ep, period, step);
}

std.ani.Interpolator.prototype.start = function(sp, ep, period, step) {
	this.stop();
	if (this._cp == null)
		this._cp = sp;
	if (sp > ep) {
		this._cp = Math.min(sp, this._cp);
		this._cp = Math.max(ep, this._cp);
	} else {
		this._cp = Math.max(sp, this._cp);
		this._cp = Math.min(ep, this._cp);
	}
	this._sp = parseFloat(sp);
	this._ep = parseFloat(ep);
	this._period = parseFloat(period);
	this._step = parseFloat(step);
	this._st = this._calStartTime();
	this._timer = setTimeout(std.lang.bind(this, this._onStep), this._step);
	this._fireEvent(true, false, false);
}

std.ani.Interpolator.prototype._onStep = function() {
	this._cp = this._calCurPoint();
	if ((this._sp > this._ep && this._cp <= this._ep) || (this._sp < this._ep && this._cp >= this._ep)) {
		this._cp = this._ep;
		this._timer = null;
		this._fireEvent(false, true, true);
	} else {
		this._timer = setTimeout(std.lang.bind(this, this._onStep), this._step);
		this._fireEvent(false, false, false);
	}
}

std.ani.Interpolator.prototype.stop = function() {
	if (this._timer != null) {
		clearTimeout(this._timer);
		this._timer = null;
		this._fireEvent(false, true, false);
	}
}

std.ani.Interpolator.prototype.reset = function(cp) {
	this.stop();
	this._cp = cp;
	this._fireEvent(false, true, false);
}

std.ani.Interpolator.prototype.getCurPoint = function() {
	return this._cp;
}

std.ani.Interpolator.prototype.setOnEvent = function(onEvent) {
	this._onEvent = onEvent;
}

std.ani.Interpolator.prototype._calStartTime = function() {
	return new Date() - this._period * (this._cp - this._sp) / (this._ep - this._sp);
}

std.ani.Interpolator.prototype._calCurPoint = function() {
	return this._sp + (this._ep - this._sp) * (new Date() - this._st) / this._period;
}

std.ani.Interpolator.prototype._fireEvent = function(isOnStart, isOnStop, isOnComplete) {
	if (this._onEvent != null)
		this._onEvent(this, isOnStart, isOnStop, isOnComplete);
}

///////////////////////////////////////////////////////////////////////////////
// Javascript Module Loading

std.loader = {
	baseUri: '/',
	_loaded: {},
	_inclusionList: []
};

// Call to include a module
std.loader.require = function(name) {
/*	if (this._loaded[name]) {
		return true;
	} else {
		var path = name.split('.');
		var uri = this.baseUri + path.join('/') + '.js';
		var contents = std.ajax.getText(uri);

		// TODO AJAX error hanlding

		var statements = contents.match(/std\.loader\.(require|provide)\s*\([^)]+\)/g) || [];
		for (var i = 0; i < statements.length; i++) {
			eval(statements[i]);
		}

		this._inclusionList.push(uri);
		return true;
	}*/
}

// Declares that a module is provided, JS not loaded by std.loader.require can call this as well.
std.loader.provide = function(name) {
	this._loaded[name] = true;
}

// Call to emit the inclusion list as <script> nodes
std.loader.conclude = function() {
	for (var i = 0; i < this._inclusionList.length; i++) {
		std.loader.loadJS(this._inclusionList[i]);
	}
	this._inclusionList = [];
}

std.loader.loadJS = function(url) {
	document.write("<script type='text/javascript' src='" + url + "'></script>");
}

/*
modified from http://www.json.org/json.js (2007-03-20)

    Public Domain

        std.json.toJSONString(obj)
            This methods produce a JSON text from a JavaScript value.
            It must not contain any cyclical references. Illegal values
            will be excluded.

            The default conversion for dates is to an ISO string.

        std.json.parseJSON(obj,filter)
            This method parses a JSON text to produce an object or
            array. It can throw a SyntaxError exception.

            The optional filter parameter is a function which can filter and
            transform the results. It receives each of the keys and values, and
            its return value is used instead of the original value. If it
            returns what it received, then structure is not modified. If it
            returns undefined then the member is deleted.

            Example:

            // Parse the text. If a key contains the string 'date' then
            // convert the value to a date.

            myData = std.json.parseJSON(text, function (key, value) {
                return key.indexOf('date') >= 0 ? new Date(value) : value;
            });
*/

std.json = {};

std.json.arrayToJSONString = function (self) {
	var a = ['['],  // The array holding the text fragments.
		b = 0,      // A boolean indicating that a comma is required.
		i,          // Loop counter.
		l = self.length;

	for (i = 0; i < l; i += 1) {
		var s = std.json.toJSONString(self[i]);
		if (b++) a.push(',');
		a.push(s);
	}

	a.push(']');
	return a.join('');
};

std.json.booleanToJSONString = function (self) {
	return String(self);
};


std.json.dateToJSONString = function (self) {
	return std.date.format(self, '"yyyy-MM-ddThh:mm:ss"');
};


std.json.numberToJSONString = function (self) {
	return isFinite(self) ? String(self) : "null";
};

std.json.toJSONString = function (self) {
	switch (typeof self) {
	case 'object':	
		if (self) {	    	
			if (typeof self.toJSONString === 'function')
				return (self.toJSONString());

			var clz = self.constructor;
			if (clz == Array)
				return std.json.arrayToJSONString(self);
			else if (clz == Date)
				return std.json.dateToJSONString(self);
			else if (clz == Object)
				return std.json.mapToJSONString(self);
		} else {
			return 'null';
		}
	case 'string':
		return std.json.stringToJSONString(self);
	case 'number':
	    return std.json.numberToJSONString(self);
	case 'boolean':
	    return std.json.booleanTOJSONString(self);
	}
};

std.json.mapToJSONString = function (self) {
	var a = ['{'],  // The array holding the text fragments.
		b = 0,      // A boolean indicating that a comma is required.
		k;          // The current key.

	for (k in self) {
		if (self.hasOwnProperty(k)) {
			var s = std.json.toJSONString(self[k]);
			if (b++) a.push(',');
			a.push(std.json.toJSONString(k), ':', s);
		}
	}

	a.push('}');
	return a.join('');
};

// This version calls eval(), no additional checking

std.json.parse = function(str) {
	var res = { json: null, error: null, isSuccess: true, isError: false };
	try {
		res.json = eval('(' + str + ')')
	} catch (e) {
		res.error = e;
		res.isSuccess = true;
		res.isError = true;
	}
	return res;
}

std.json.parseJSON = function (self, filter) {

// Parsing happens in three stages. In the first stage, we run the text against
// a regular expression which looks for non-JSON characters. We are especially
// concerned with '()' and 'new' because they can cause invocation, and '='
// because it can cause mutation. But just to be safe, we will reject all
// unexpected characters.

    try {
	if (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(self)) {

// In the second stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

	    var j = eval('(' + self + ')');

// In the optional third stage, we recursively walk the new structure, passing
// each name/value pair to a filter function for possible transformation.

	    if (typeof filter === 'function') {
/*			function walk(k, v) {
				if (v && typeof v === 'object') {
					for (var i in v)
					if (v.hasOwnProperty(i)) {
						v[i] = walk(i, v[i]);
					}
				}
				return filter(k, v);
			}

			j = walk('', j);
*/			
			std.log.warn('std.json.parseJSON(): filter not supported');
	    }
	    return j;
	}
    } catch (e) {

// Fall through if the regexp test fails.

    }
    throw new SyntaxError("parseJSON");
};

std.json.stringToJSONString = function (self) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can simply slap some quotes around it.
// Otherwise we must also replace the offending characters with safe
// sequences.

    if (/["\\\x00-\x1f]/.test(self)) {
		return '"' + self.replace(/([\x00-\x1f\\"])/g, std.json._replaceFunc) + '"';
    }
    return '"' + self + '"';
};

std.json._replaceFunc = function(a, b) {
	var m = {
    	'\b': '\\b',
    	'\t': '\\t',
    	'\n': '\\n',
    	'\f': '\\f',
    	'\r': '\\r',
    	'"' : '\\"',
		'\\': '\\\\'
	};
	var c = m[b];
	if (c) {
		return c;
	}
	c = b.charCodeAt();
	return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
};

///////////////////////////////////////////////////////////////////////////////
// A Generic Action Framework

std.action = {};

/**
 * @class Base class for all actions
 */
std.action.AbstractAction = function() {}

std.action.AbstractAction.prototype._initAbstractAction = function(options) {
	options = options || {};

	this._icon = options.icon || "";
	this._text = options.text || "";
	this._mnemonic = options.mnemonic || "";
	this._selected = options.selected || "";
	this._shortcutList = options.shortcutList || [];
	this._disabled = options.disabled || false;

	this._selected = null;
}

std.action.AbstractAction.prototype.getIcon = function() {
	return std.lang.evaluate(this._icon);
}

/**
 * Gets the action text.
 * @return The text.
 */
std.action.AbstractAction.prototype.getText = function() {
	return std.lang.evaluate(this._text);
}

/**
 * Gets the action mnemonic.
 * @return the mnemonic character.
 */
std.action.AbstractAction.prototype.getMnemonic = function() {
	return std.lang.evaluate(this._mnemonic);
}

std.action.AbstractAction.prototype.isSelected = function() {
	return this._selected;
}

std.action.AbstractAction.prototype.setSelected = function(value) {
	this._selected = value;
}

/**
 * Gets all the shortcut keys associated with this action.
 * @return {std.action.KeyComb[]} An array of shortcut keys.
 */
std.action.AbstractAction.prototype.getShortcutList = function() {
	return this._shortcutList;
}

/**
 * Sets the shortcut keys associated with this action.
 * @param {std.action.KeyComb[]} list An array of shortcut keys.
 */
std.action.AbstractAction.prototype.setShortcutList = function(list) {
	this._shortcutList = list || [];
}

/**
 * Adds a shortcut key to the internal key list.
 * @param {std.action.KeyComb} shortcut The shortcut key.
 */
std.action.AbstractAction.prototype.addShortcut = function(shortcut) {
	this._shortcutList.push(shortcut);
}

/**
 * Checks if the action is disabled.
 * @return true if disabled.
 */
std.action.AbstractAction.prototype.isDisabled = function() {
	return std.jexpr.evaluate(this._disabled);
}

/**
 * Sets whether the action is disabled.
 * @param value If is disabled.
 */
std.action.AbstractAction.prototype.setDisabled = function(value) {
	this._disabled = value;
}

std.action.AbstractAction.prototype.getValue = function() {
	return this._value;
}

std.action.AbstractAction.prototype.setValue = function(value) {
	this._value = value;
}

/**
 * Performs the action.
 */
std.action.AbstractAction.prototype.performAction = function() {}

/**
 * Constructor.
 * @param {object} options An options hash.
 * @class A nil action that does nothing when fired. Usually used as a placeholder.
 * @augments std.action.AbstractAction
 */
std.action.Nil = function(options) {
	this._initAbstractAction(options);
}

std.action.Nil.prototype = new std.action.AbstractAction();

/**
 * Constructor. The script to execute is provided using the 'script' option.
 * @param {object} options An options hash.
 * @class A script action that executes a script when fired.
 * @augments std.action.AbstractAction
 */
std.action.Script = function(options) {
	this._initAbstractAction(options);
	this._script = options.script;
}

std.action.Script.prototype = new std.action.AbstractAction();

std.action.Script.prototype.performAction = function() {
	this._script.call(null, this);
}

/**
 * Constructor. The url to open is provided using the 'url' option.
 * @param {object} options An options hash.
 * @class A url action that opens a url when fired.
 * @augments std.action.AbstractAction
 */
std.action.Url = function(options) {
	this._initAbstractAction(options);
	this._url = options.url;
	this._popup = options.popup;
}

std.action.Url.prototype = new std.action.AbstractAction();

std.action.Url.prototype.performAction = function() {
	std.host.open(std.lang.evaluate(this._url), this._popup);
}

/**
 * Constructor
 * @param keys A string representation of a key combination. e.g. "ALT SHIFT C"
 * @class Represents a key combination.
 */
std.action.KeyComb = function(keys) {

	this._key = null;
	this._reqCtrl = false;
	this._reqAlt = false;
	this._reqShift = false;

	std.list.iterate(keys.toUpperCase().split(' '), std.lang.bind(this, function(key) {
		switch (key) {
			case 'CTRL':
				this._reqCtrl = true; break;
			case 'ALT':
				this._reqAlt = true; break;
			case 'SHIFT':
				this._reqShift = true; break;
			default:
				this._key = std.keytable[key];
		}
	}));

}

/**
 * Converts the key combination to a human readable string.
 * @return The converted string.
 */
std.action.KeyComb.prototype.toString = function() {

	if (!this._string) {

		var keys = [];
		if (this._reqCtrl) keys.push('Ctrl');
		if (this._reqAlt) keys.push('Alt');
		if (this._reqShift) keys.push('Shift');
		keys.push(this._key[1]);
		this._string = keys.join('+');

	}
	
	return this._string;
}

/**
 * Checks if the key combination is equivalent to the supplied arguments.
 * @param keyCode The key code of the pressing key.
 * @param ctrlKey If the CTRL modifier key is pressed.
 * @param altKey If the ALT modifier key is pressed.
 * @param shiftKey If the SHIFT modifier key is pressed.
 * @param metaKey If the META modifier key is pressed.
 * @return true if equal.
 */
std.action.KeyComb.prototype.check = function(keyCode, ctrlKey, altKey, shiftKey, metaKey) {

	// normalize input
	ctrlKey = ctrlKey ? true : false;
	altKey = altKey ? true : false;
	shiftKey = shiftKey ? true : false;

	return (ctrlKey == this._reqCtrl || (std.browser.platform.isMacOS && (metaKey == this._reqCtrl))) &&
		altKey == this._reqAlt &&
		shiftKey == this._reqShift &&
		keyCode == this._key[0];
}

