/*!
 * Zedcore CMS javascript framework.
 * Copyright 2009-2010 Zedcore Systems Ltd
 */

/**
 * Base classes for our javascript framework
 * @module Base
 */
if (typeof ZC === "undefined" || !ZC) {
    /**
     * The ZC global namespace object.  If ZC is already defined, the
     * existing ZC object will not be overwritten so that defined
     * namespaces are preserved.
	 * @class ZC
	 * @namespace
     * @static
     */
    var ZC = {};
}

/**
 * The following is based on the YAHOO.namespace function in YUI. It's
 * basically the same, but uses ZC as the namespace instead.
 * @method Namespace
 * @param  {String*} arguments 1-n namespaces to create
 * @return {Object}  A reference to the last namespace object created
 */
ZC.Namespace = function() {
    var a=arguments, o=null, i, j, d;
    for (i=0; i<a.length; i=i+1) {
        d=a[i].split(".");
        o=ZC;

        // ZC is implied, so it is ignored if it is included
        for (j=(d[0] == "ZC") ? 1 : 0; j<d.length; j=j+1) {
            o[d[j]]=o[d[j]] || {};
            o=o[d[j]];
        }
    }

    return o;
};

(function(){
// aliases for oft-used objects
var L = YAHOO.lang, Dom = YAHOO.util.Dom, Evt = YAHOO.util.Event;

var _ForEachArray = function(aArray, fnCallback, oThis)
{
	if (Array.forEach)
		_ForEachArray = Array.forEach;
	else
		_ForEachArray = function (aArray, fnCallback, oThis)
		{
			if (!L.isFunction(fnCallback))
				throw new TypeError();

			for (var i = 0, iMax = aArray.length; i < iMax; i++)
			{
				if (i in aArray)
					fnCallback.call(oThis, aArray[i], i, aArray);
			}
		}

	_ForEachArray(aArray, fnCallback, oThis);
}

// from dojo - used by the clone method below
var extraNames, extraLen, empty = {};
for(var i in {toString: 1}){ extraNames = []; break; }
extraNames = extraNames || ["hasOwnProperty", "valueOf", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString", "toString", "constructor"];
extraLen = extraNames.length;

// private var for GetScrollbarWidth
var iScrollbarWidth;

var sprintfRegex = /%%|%(\d+\$)?([-+#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuidfegEG])/g;
/**
 * @class Util
 * @namespace ZC
 * @static
 */
ZC.Util = {
	
	sCurrentDomain: 'Core',
	
	/* * * * I18N UTILS * * * */
	/* See http://office.zedcore.com/wiki/index.php/CMS:Spec:Javascript_L10N */
	
	/**
	 * Javascript sprintf, from http://hexmen.com/js/sprintf.js
	 * @param {String} sFormat format string
	 * @param {mixed} [...] parameters
	 */
	sprintf: function() {
		function pad(str, len, chr, leftJustify) {
			var padding = (str.length >= len) ? '' : Array(1 + len - str.length >>> 0).join(chr);
			return leftJustify ? str + padding : padding + str;
		}

		function justify(value, prefix, leftJustify, minWidth, zeroPad) {
			var diff = minWidth - value.length;
			if (diff > 0) {
				if (leftJustify || !zeroPad) {
				value = pad(value, minWidth, ' ', leftJustify);
				} else {
				value = value.slice(0, prefix.length) + pad('', diff, '0', true) + value.slice(prefix.length);
				}
			}
			return value;
		}

		function formatBaseX(value, base, prefix, leftJustify, minWidth, precision, zeroPad) {
			// Note: casts negative numbers to positive ones
			var number = value >>> 0;
			prefix = prefix && number && {'2': '0b', '8': '0', '16': '0x'}[base] || '';
			value = prefix + pad(number.toString(base), precision || 0, '0', false);
			return justify(value, prefix, leftJustify, minWidth, zeroPad);
		}

		function formatString(value, leftJustify, minWidth, precision, zeroPad) {
			if (precision != null) {
				value = value.slice(0, precision);
			}
			return justify(value, '', leftJustify, minWidth, zeroPad);
		}

		var a = arguments, i = 0, format = a[i++];
		return format.replace(sprintfRegex, function(substring, valueIndex, flags, minWidth, ignore, precision, type) {
			if (substring == '%%') return '%';

			// parse flags
			var leftJustify = false, positivePrefix = '', zeroPad = false, prefixBaseX = false;
			for (var j = 0; flags && j < flags.length; j++) switch (flags.charAt(j)) {
				case ' ': positivePrefix = ' '; break;
				case '+': positivePrefix = '+'; break;
				case '-': leftJustify = true; break;
				case '0': zeroPad = true; break;
				case '#': prefixBaseX = true; break;
			}

			// parameters may be null, undefined, empty-string or real valued
			// we want to ignore null, undefined and empty-string values

			if (!minWidth) {
				minWidth = 0;
			} else if (minWidth == '*') {
				minWidth = +a[i++];
			} else if (minWidth.charAt(0) == '*') {
				minWidth = +a[minWidth.slice(1, -1)];
			} else {
				minWidth = +minWidth;
			}

			// Note: undocumented perl feature:
			if (minWidth < 0) {
				minWidth = -minWidth;
				leftJustify = true;
			}

			if (!isFinite(minWidth)) {
				throw new Error('sprintf: (minimum-)width must be finite');
			}

			if (!precision) {
				precision = 'fFeE'.indexOf(type) > -1 ? 6 : (type == 'd') ? 0 : void(0);
			} else if (precision == '*') {
				precision = +a[i++];
			} else if (precision.charAt(0) == '*') {
				precision = +a[precision.slice(1, -1)];
			} else {
				precision = +precision;
			}

			// grab value using valueIndex if required?
			var value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];

			switch (type) {
			case 's': return formatString(String(value), leftJustify, minWidth, precision, zeroPad);
			case 'c': return formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad);
			case 'b': return formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
			case 'o': return formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
			case 'x': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
			case 'X': return formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad).toUpperCase();
			case 'u': return formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
			case 'i':
			case 'd': {
					  var number = parseInt(+value);
					  var prefix = number < 0 ? '-' : positivePrefix;
					  value = prefix + pad(String(Math.abs(number)), precision, '0', false);
					  return justify(value, prefix, leftJustify, minWidth, zeroPad);
				  }
			case 'e':
			case 'E':
			case 'f':
			case 'F':
			case 'g':
			case 'G':
					  {
					  var number = +value;
					  var prefix = number < 0 ? '-' : positivePrefix;
					  var method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
					  var textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
					  value = prefix + Math.abs(number)[method](precision);
					  return justify(value, prefix, leftJustify, minWidth, zeroPad)[textTransform]();
				  }
			default: return substring;
			}
		});
	},

	/**
	 * Repeats a string
	 *
	 * @param sStr the string to be repeated
	 * @param iMultiplier Number of times to repeat the string
	 */
	StrRepeat: function(sStr, iMultiplier)
	{
		if (iMultiplier < 0)
			YAHOO.log('StrRepeat: iMultiplier should be positive', 'error');
		return new Array(iMultiplier + 1).join('*');
	},
	
	/**
	 * Sets the translation domain to search for translations when calls are made to GetText and family
	 *
	 * @param {String} } sTextDomain the domain to use for future translations. Omit for no change.
	 * @return {String}  The text domain that will be used for future translations
	 */
	 TextDomain: function (sTextDomain)
	 {
	 	 if(!L.isUndefined(sTextDomain))
		 	this.sCurrentDomain = sTextDomain;
		 return this.sCurrentDomain;
	 },

	/**
	 * Translates a string.
	 *
	 * @param {String} sStr the string to translate
	 * @return {String} the translated string, or the original string if there's no translation available
	 */
	GetText: function (sStr)
	{        
		var sTextDomain = ZC.Util.TextDomain();
		if (!L.isUndefined(ZC.oTranslations))
		{
			try
			{
				if(!L.isUndefined(ZC.oTranslations[sTextDomain][sStr][1]) && ZC.oTranslations[sTextDomain][sStr][1])
					sStr = ZC.oTranslations[sTextDomain][sStr][1];			
			}
			catch (e ) 
			{
				// This is the only cross-browser way to specify which exceptions are supposed to be caught...
				if (!(e instanceof TypeError))
					throw e;	
			}
		}
			
		return sStr;
	},

	/**
	 * Translates a string, using correct plural form (some languages have up to 4 different options!)
	 *
	 * @param {String} sSingStr the string to translate, singular form
	 * @param {String} sPlurStr the string to translate, plural form
	 * @param {Number} iCount the number to return the string for
	 * @return {String} the translated string, or the original string if there's no translation available
	 */
	NGetText: function (sSingStr, sPlurStr, iCount)
	{
		var sString, nplurals, plural, n = iCount, sTextDomain = ZC.Util.TextDomain();
		
		if (!L.isUndefined(ZC.oTranslations))
		{
			// Try to parse out which plural form this string should be in.
			try
			{
				// This should set nplural - the number of plural forms in this language and also given n == iCount set plural to the correct plural form number.
				eval(ZC.oTranslations[sTextDomain][""]['Plural-Forms']);
				plural = Number(plural);
			}
			catch (e){}
			finally
			{
				// If the plural form could not be figured out or is invalid, default to English/Western Europe style
				if(!L.isNumber(nplurals) || !L.isNumber(plural) || plural > nplurals)
				{
					nplurals = 2;
					plural = (iCount == 1) ? 0 : 1;
				}	
			}                                               
			
			try
			{
				if(!L.isUndefined(ZC.oTranslations[sTextDomain][sSingStr][plural +1]) && ZC.oTranslations[sTextDomain][sSingStr][plural +1])
					sString = ZC.oTranslations[sTextDomain][sSingStr][plural +1];			
			}
			catch (e ) 
			{
				if (!(e instanceof TypeError))
					throw e;	
			}
		}

		if(!sString)
		{
			if(iCount == 1)
				sString = sSingStr;
			else
				sString = sPlurStr;	
		}
		
		return sString;
	},
	
	/**
	 * Translates a string.
	 *
	 * @param {String} sDomain the translation domain to look up the translate in
	 * @param {String} sStr the string to translate
	 * @return {String} the translated string, or the original string if there's no translation available
	 */
	 DGetText: function (sDomain, sStr)
	 {
	 	 var sOldDomain = ZC.Util.TextDomain(), sTranslatedStr;
	 	 ZC.Util.TextDomain(sDomain);
	 	 sTranslatedStr = ZC.Util.GetText(sStr);
	 	 ZC.Util.TextDomain(sOldDomain);
	 	 return sTranslatedStr;
	 },
	 
	 /**
	 * Translates a string.
	 *
	 * @param {String} sDomain the translation domain to look up the translate in
	 * @param {String} sSingStr the string to translate, singular form
	 * @param {String} sPlurStr the string to translate, plural form
	 * @param {Number} iCount the number to return the string for
	 * @return {String} the translated string, or the original string if there's no translation available
	 */
	 DNGetText: function (sDomain, sSingStr, sPlurStr, iCount)
	 {
	 	 var sOldDomain = ZC.Util.TextDomain(), sTranslatedStr;
	 	 ZC.Util.TextDomain(sDomain);
	 	 sTranslatedStr = ZC.Util.NGetText(sSingStr, sPlurStr, iCount);
	 	 ZC.Util.TextDomain(sOldDomain);
	 	 return sTranslatedStr;
	 },
	
	 /**
	  * Replaces {tags} in sContent with values from aTags. Should work the same as the CMS function of the same name.
	  *
	  * @param {String} sContent content to replace tags in
	  * @param {Array} aTags values for tags
	  * @param {Boolean} bBlankUnknownTags if true, then unknown tags are replaced with the empty string. If false (default), they are left intact.
	  */
	ReplaceTags: function(sContent, aTags, bBlankUnknownTags)
	{
		var fnReplace = function(sStr, sTagName) 
		{ 
			if (L.isUndefined(aTags[sTagName]))
				return bBlankUnknownTags ? "" : sStr;
		   
			return aTags[sTagName];
		}

		// Replace anything in between braces with the corresponding value in $aTags
		sContent = sContent.replace(/\{(.*?)\}/gi, fnReplace);

		// HTML editor prefixes URLs with / when we use {tagname} as a URL. Correct the result.
		sContent = sContent.replace('"/http', '"http');

		return sContent;
	},

	

	/* * * * ANIMATION UTILS * * * */

	/**
	 * Wraps the contents of an existing element in a &lt;div&gt; tag, to make
	 * animation easier (table cells have issues).
	 *
	 * e.g:
	 * <pre>&lt;td&gt;&lt;strong&gt;Hello world&lt;/strong&gt;&lt;/td&gt;</pre> 
	 * becomes:
	 * <pre>&lt;td&gt;&lt;div&gt;&lt;strong&gt;Hello world&lt;/strong&gt;&lt;/div&gt;&lt;/td&gt;</pre>
	 *
	 * @param {HTMLElement} elSrc source element to wrap the contents of
	 * @return {HTMLElement} the new wrapping element
	 */
	WrapContents: function(elSrc)
	{
		var	aChildren = elSrc.childNodes,
			elDiv = document.createElement('div');

		while(aChildren.length)
			elDiv.appendChild(aChildren[0]);

		elSrc.appendChild(elDiv);
		return elDiv;
	},

	/**
	 * Scrolls the current window to the position indicated by mTo.
	 * Requires the YUI Animation module to be loaded.
	 *
	 * @param {mixed} mTo Can either be an array of two integers [x, y] describing the position to scroll to, or a HTML element (in which case the window is scrolled to that element)
	 * @param [{Number}] fDuration Animation duration in seconds (default = 1)
	 * @param [{Function}] fnEasing The easing function to use, probably from YAHOO.util.Easing (default = YAHOO.util.Easing.easeOut)
	 */
	ScrollPage: function(mTo, fDuration, fnEasing)
	{
		var aToXY, oAnim;

		if (L.isArray(mTo))
		{
			aToXY = mTo;
		}
		else
		{
			aToXY = Dom.getXY(mTo);
		}

		if (L.isUndefined(fDuration))
			fDuration = 1;

		if (L.isUndefined(fnEasing))
			fnEasing = YAHOO.util.Easing.easeOut;

		if (aToXY)
		{
			if (L.isUndefined(this.bUseDocumentElement))
			{
				// Need to use document.documentElement in Standards Compliance mode, and document.body in Quirks mode
				// If in the wrong mode, then setting scrollTop will have no effect, so we can use this to test which one to use.
				var iExistingScrollTop = document.documentElement.scrollTop;

				document.documentElement.scrollTop = iExistingScrollTop + 1;
				this.bUseDocumentElement = (document.documentElement.scrollTop == iExistingScrollTop + 1);
				document.documentElement.scrollTop = iExistingScrollTop;
			}

			oAnim = new YAHOO.util.Scroll(this.bUseDocumentElement ? document.documentElement : document.body, { scroll: { to : aToXY }  }, fDuration, fnEasing);
			oAnim.animate();  
		}
	},

	/**
	 * Updates an element on the page, with optional animation.
	 *
	 * Attribs recognised:
	 * <dl>
	 * <dt>HTML			</dt><dd> The new HTML to update the element with 										</dd>
	 * <dt>Animation	</dt><dd> Override the animation type. (one of: "none" or "fade")						</dd>
	 * <dt>Scope		</dt><dd> Scope for the On* functions. (defaults to the element we're updating)			</dd>
	 * <dt>OnStart   	</dt><dd> Function to run when starting the update.										</dd>
	 * <dt>OnHalfway 	</dt><dd> Function to run when halfway (old element gone, new element about to appear).	</dd>
	 * <dt>OnComplete	</dt><dd> Function to run when done.													</dd>
	 * </dl>
	 *
	 * NOTE: HTML is optional, you may want a custom method of updating the element (using OnHalfway), but still have the animation, etc.
	 *
	 * @param {String/HTMLElement} sID the element id, or element object
	 * @param {Object} oAttribs attribs for defining the update
	 */
	UpdateElement: function(sID, oAttribs)
	{
		var el = Dom.get(sID), 
			sAnimation, oAnimOut, oAnimIn;

		if (L.isUndefined(oAttribs.OnStart))
		{
			oAttribs.OnStart = function() {}
		}
		if (L.isUndefined(oAttribs.OnHalfway))
		{
			oAttribs.OnHalfway = function() {}
		}
		if (L.isUndefined(oAttribs.OnComplete))
		{
			oAttribs.OnComplete = function() {}
		}
		if (L.isUndefined(oAttribs.Scope))
		{
			oAttribs.Scope = el;
		}


		if (el)
		{
			sAnimation = 'fade';
			// IE seems to break with the "fade" animation, so force no animation for IE users.
			if (YAHOO.env.ua.ie || !YAHOO.env.getVersion('animation') || Dom.hasClass(el, 'hidden') || Dom.hasClass(el, 'hide'))
			{
				sAnimation = 'none';
			}
			else if (!L.isUndefined(oAttribs.Animation))
			{
				sAnimation = oAttribs.Animation;
			}

			switch (sAnimation)
			{
				case 'fade':
					oAnimIn = new YAHOO.util.Anim(el, { opacity: { to: 1 } }, 0.1);
					oAnimOut = new YAHOO.util.Anim(el, { opacity: { from: Dom.getStyle(el, 'opacity'), to: 0 } }, 0.1);

					oAnimOut.onStart.subscribe(oAttribs.OnStart, oAttribs.Scope, true);
					oAnimOut.onComplete.subscribe(function() { 
						if (!L.isUndefined(oAttribs.HTML))
							el.innerHTML = oAttribs.HTML;
						oAttribs.OnHalfway.call(oAttribs.Scope);
						ZC.JSManager.AutoTooltips(el);
						oAnimIn.animate();
					});
					oAnimIn.onComplete.subscribe(function() { 
						oAttribs.OnComplete.call(oAttribs.Scope);
						L.later(10, el, function() { Dom.setStyle(this, 'opacity', ''); });
					}, this, true);
					oAnimOut.animate();
					break;

				case 'none': 
				default:
					oAttribs.OnStart.call(oAttribs.Scope);
					oAttribs.OnHalfway.call(oAttribs.Scope);
					
					if (!L.isUndefined(oAttribs.HTML))
						el.innerHTML = oAttribs.HTML;

					ZC.JSManager.AutoTooltips(el);
					oAttribs.OnComplete.call(oAttribs.Scope);
					break;
			}
		}
		else
		{
			YAHOO.log('UpdateElement: unable to locate element with id ' + sID, 'warn', 'UpdateElement');

			oAttribs.OnStart.call(oAttribs.Scope);
			oAttribs.OnHalfway.call(oAttribs.Scope);
			oAttribs.OnComplete.call(oAttribs.Scope);
		}
	},

	/* * * * ARRAY UTILS * * * */

	/**
	 * Searches an array for a value.
	 * @param Value value to search for
	 * @param {Array} aSearch array to search
	 * @return {Boolean} true if found
	 */
	InArray: function (Value, aSearch)
	{
		return (this.IndexOf(aSearch, Value) >= 0);
	},

	/*
	 * the following methods are present in Javascript 1.6, which is currently supported in Firefox 1.5+, but (afaik) no other browsers yet.
	 * The MDC javascript reference contains sample implementations for all of these methods, which is where I got the code from
	 * (see http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array)
	 *
	 * I've made ForEach able to take an array OR an object. The others can be similarly adapted, but again it's not required currently, so I've not done it.
	 *
	 * I'm using a lazy binding technique to make use of the (faster) browser-based implementations where available
	 */

	/**
	 * Returns the first index at which a given element can be found in the array, or -1 if it is not present.
	 * Where available, this is just an alias for Array.indexOf
	 *
	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/indexOf">Array.indexOf reference @ MDC</a>
	 * @param {Array} aSearch array to search
	 * @param SearchElement Element to locate in the array.
	 * @param {Number} fromIndex The index at which to begin the search.
	 */
	IndexOf: function(aSearch, SearchElement, iFrom)
	{
		if (Array.indexOf)
			this.IndexOf = Array.indexOf;
		else
			this.IndexOf = function(aSearch, SearchElement, iFrom)
			{
				iFrom = Number(iFrom) || 0;
				var iLen = aSearch.length;
				if (iFrom < 0)
				  iFrom += iLen;

				for (; iFrom < iLen; iFrom++)
				{
				  if (iFrom in aSearch &&
					  aSearch[iFrom] === SearchElement)
					return iFrom;
				}
				return -1;
			}

		return this.IndexOf(aSearch, SearchElement, iFrom);
	},

	/**
	 * Returns the last index at which a given element can be found in the array, or -1 if it is not present.
	 * Where available, this is just an alias for Array.lastIndexOf
	 *
	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/lastIndexOf">Array.lastIndexOf reference @ MDC</a>
	 * @param {Array} aSearch array to search
	 * @param SearchElement Element to locate in the array.
	 * @param {Number} fromIndex The index at which to begin the search.
	 */
	LastIndexOf: function(aSearch, SearchElement, iFrom)
	{
		if (Array.indexOf)
			this.LastIndexOf = Array.lastIndexOf;
		else
			this.LastIndexOf = function(aSearch, SearchElement, iFrom)
			{
				var iLen = aSearch.length;
				iFrom = Number(iFrom) || 0;
				if (isNaN(iFrom))
					iFrom = iLen - 1;
				else
				{
					if (iFrom < 0)
						iFrom += iLen;
					else if (iFrom >= iLen)
						iFrom = iLen - 1;
				}

				for (; iFrom > -1; iFrom--)
				{
				  if (iFrom in aSearch &&
					  aSearch[iFrom] === SearchElement)
					return iFrom;
				}
				return -1;
			}

		return this.LastIndexOf(aSearch, SearchElement, iFrom);
	},

	/**
	 * Executes a provided function once per array element.
	 * Where available, this is just an alias for Array.forEach
	 * Also works with objects, unlike Array.forEach.
	 *
	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/forEach">Array.forEach reference @ MDC</a>
	 * @param {Array/Object} aArray the array or object to iterate through
	 * @param {Function} fnCallback Function to execute for each element.
	 * @param {Object} oThis Object to use as 'this' when executing callback.
	 */
	ForEach: function(aArray, fnCallback, oThis)
	{
		if (L.isUndefined(aArray))
			return;

		if (!L.isFunction(fnCallback))
			throw new TypeError();

		if (L.isArray(aArray))
			return _ForEachArray(aArray, fnCallback, oThis);

		if (!L.isUndefined(aArray.length))
		{
			// handles IE's HTML element collections
			for (var i = 0, iMax = aArray.length; i < iMax; i++)
			{
				fnCallback.call(oThis, aArray[i], i, aArray);
			}
		}
		else
		{
			for (var Key in aArray)
			{
				if (L.hasOwnProperty(aArray, Key))
				{
					fnCallback.call(oThis, aArray[Key], Key, aArray);
				}
			}
		}
	},

	/**
	 * Creates a new array with the results of calling a provided function on every element in this array.
	 * Where available, this is just an alias for Array.map
	 *
	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/map">Array.map reference @ MDC</a>
	 * @param {Array} aArray array to work on
	 * @param {Function} fnCallback Function that produces an element of the new Array from an element of the current one.
	 * @param {Object} oThis Object to use as 'this' when executing callback.
	 */
	Map: function(aArray, fnCallback, oThis)
	{
		if (Array.map)
			this.Map = Array.map;
		else
			this.Map = function(aArray, fnCallback, oThis)
			{
				if (!L.isFunction(fnCallback))
					throw new TypeError();

				var iLen = aArray.length;

				var aResult = new Array(iLen);
				for (var i = 0; i < iLen; i++)
				{
					if (i in aArray)
						aResult[i] = fnCallback.call(oThis, aArray[i], i, aArray);
				}

				return aResult;
			}

		return this.Map(aArray, fnCallback, oThis);
	},

	/**
	 * Creates a new array with all elements that pass the test implemented by the provided function.
	 * Where available, this is just an alias for Array.filter
	 *
	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter">Array.filter reference @ MDC</a>
	 * @param {Array} aArray array to work on
	 * @param {Function} fnCallback Function to test each element of the array.
	 * @param {Object} oThis Object to use as 'this' when executing callback.
	 */
	Filter: function(aArray, fnCallback, oThis)
	{
		if (Array.filter)
			this.Filter = Array.filter;
		else
			this.Filter = function(aArray, fnCallback, oThis)
			{
				if (!L.isFunction(fnCallback))
					throw new TypeError();

				var iLen = aArray.length;

				var aResult = new Array();
				for (var i = 0; i < iLen; i++)
				{
					if (i in aArray)
					{
						var Val = aArray[i];
						if (fnCallback.call(oThis, Val, i, aArray))
							aResult.push(Val);
					}
				}

				return aResult;
			}

		return this.Filter(aArray, fnCallback, oThis);
	},

	/**
	 * Tests whether some element in the array passes the test implemented by the provided function.
	 * Where available, this is just an alias for Array.some
	 *
	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/some">Array.some reference @ MDC</a>
	 * @param {Array} aArray array to test
	 * @param {Function} fnCallback Function to test for each element.
	 * @param {Object} oThis Object to use as this when executing callback.
	 */
	Some: function(aArray, fnCallback, oThis)
	{
		if (Array.some)
			this.Some = Array.some;
		else
			this.Some = function(aArray, fnCallback, oThis)
			{
				if (!L.isFunction(fnCallback))
					throw new TypeError();

				var iLen = aArray.length;

				for (var i = 0; i < iLen; i++)
				{
					if (i in aArray && fnCallback.call(oThis, aArray[i], i, aArray))
						return true;
				}

				return false;
			}

		return this.Some(aArray, fnCallback, oThis);
	},

	/**
	 * Tests whether all elements in the array pass the test implemented by the provided function.
	 * Where available, this is just an alias for Array.every
	 *
	 * @see <a href="http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/every">Array.every reference @ MDC</a>
	 * @param {Array} aArray array to test
	 * @param {Function} fnCallback Function to test for each element.
	 * @param {Object} oThis Object to use as this when executing callback.
	 */
	Every: function(aArray, fnCallback, oThis)
	{
		if(Array.every)
			this.Every = Array.every;
		else
			this.Every = function(aArray, fnCallback, oThis)
			{
				if (!L.isFunction(fnCallback))
					throw new TypeError();

				var iLen = aArray.length;

				for (var i = 0; i < iLen; i++)
				{
					if (i in aArray && !fnCallback.call(oThis, aArray[i], i, aArray))
					return false;
				}
				return true;
			}

		return this.Every(aArray, fnCallback, oThis);
	},

	/**
	 * Returns the keys in the object or array o.
	 * @param {Object/Array} o
	 * @return {Array} the keys from the object
	 */
	Keys: function (o)
	{
		var aKeys = [];
		this.ForEach(o, function (v, k) { aKeys.push(k); });

		return aKeys;
	},

	/**
	 * Flattens an array of arrays (or a mixture of values/arrays) to a single array.
	 * @param {Array} aFlatten array to flatten
	 * @return {Array} the flattened array
	 */
	Flatten: function (aFlatten)
	{
		var aResult = [];
		U.ForEach(aFlatten, function(mVal)
		{
			if (L.isArray(mVal))
				aResult.push.apply(aResult, this.Flatten(mVal));
			else
				aResult.push(mVal);
		}, this);
		return aResult;
	},

	/**
	 * Checks for object equality. In javascript (a == b) with two objects
	 * checks that a is the same object as b (instead of checking for two
	 * instances of the same object). This method checks each property of a and
	 * b to see if they're equal.
	 * @param {Object/Array} oA first object to test
	 * @param {Object/Array} oB second object to test
	 * @param {Array} aSeen (internal) used when recursing to avoid loops
	 */
	ObjectsEqual: function (oA, oB, aSeen)
	{
		if (L.isArray(oA) != L.isArray(oB) || typeof oA != typeof oB)
			return false;

		var aKeysA = this.Keys(oA),
			aKeysB = this.Keys(oB),
			i, iMax, k, iIndexB;
		if (aKeysA.length != aKeysB.length)
			return false;

		for (i = 0, iMax = aKeysA.length; i < iMax; i++)
		{
			k = aKeysA[i];
			iIndexB = this.IndexOf(aKeysB, k);
			if (iIndexB == -1)
				return false;

			aKeysB.splice(iIndexB, 1);
			if (typeof oA[k] != typeof oB[k])
				return false;

			if (typeof oA[k] == 'object')
			{
			   	if (L.isUndefined(aSeen) || (!this.InArray(oA[k], aSeen) && !this.InArray(oB[k], aSeen)))
				{
					var aNewSeen = aSeen || [];
					aNewSeen.unshift(oA, oB);
					if (!this.ObjectsEqual(oA[k], oB[k], aNewSeen))
						return false;
				}
			}
			else
			{
				if (oA[k] != oB[k])
					return false;
			}
		}

		if (aKeysB.length > 0)
			return false;

		return true;
	},

	/**
	 * Clones an object
	 *
	 * Taken from dojo, which is BSD-licensed.
	 *
	 * @param {Object} o object to clone
	 * @return {Object} the clone
	 */
	Clone: function(o){
		// summary:
		//		Clones objects (including DOM nodes) and all children.
		//		Warning: do not clone cyclic structures.
		if(!o || typeof o != "object" || L.isFunction(o)){
			// null, undefined, any non-object, or function
			return o;	// anything
		}
		if(o.nodeType && "cloneNode" in o){
			// DOM Node
			return o.cloneNode(true); // Node
		}
		if(o instanceof Date){
			// Date
			return new Date(o.getTime());	// Date
		}
		var r, i, l, s, name;
		if(L.isArray(o)){
			// array
			r = [];
			for(i = 0, l = o.length; i < l; ++i){
				if(i in o){
					r.push(this.Clone(o[i]));
				}
			}
// we don't clone functions for performance reasons
//		}else if(L.isFunction(o)){
//			// function
//			r = function(){ return o.apply(this, arguments); };
		}else{
			// generic objects
			r = o.constructor ? new o.constructor() : {};
		}
		for(name in o){
			// the "tobj" condition avoid copying properties in "source"
			// inherited from Object.prototype.  For example, if target has a custom
			// toString() method, don't overwrite it with the toString() method
			// that source inherited from Object.prototype
			s = o[name];
			if(!(name in r) || (r[name] !== s && (!(name in empty) || empty[name] !== s))){
				r[name] = this.Clone(s);
			}
		}
		// IE doesn't recognize some custom functions in for..in
		if(extraLen){
			for(i = 0; i < extraLen; ++i){
				name = extraNames[i];
				s = o[name];
				if(!(name in r) || (r[name] !== s && (!(name in empty) || empty[name] !== s))){
					r[name] = s; // functions only, we don't clone them
				}
			}
		}
		return r; // Object
	},

	/* * * * MISC UTILS * * * */
	/**
	 * Cross-browser method for inserting text at the current cursor position
	 *
	 * NOTE: this (or at least the caret-location part of it) should be added to YUI at some point, so we should be able to move to that.
	 *
	 * @param {HTMLElement} elInput the textarea or text input element to insert the string into
	 * @param {String} sText text to insert
	 */
	InsertAtCursor: function(elInput, sText)
	{
		var iScrollPos = elInput.scrollTop, 
			iStrPos = 0,
			bSelStart = !L.isUndefined(elInput.selectionStart),
			oRange, sStart, sEnd;	

		if (bSelStart)
		{
			iStrPos = elInput.selectionStart;
		}
		else
		{
			elInput.focus();
			oRange = document.selection.createRange();
			oRange.moveStart ('character', -elInput.value.length);
			iStrPos = oRange.text.length;
		}

		sStart = elInput.value.substring(0,iStrPos);  
		sEnd = elInput.value.substring(iStrPos); 
		elInput.value = sStart + sText + sEnd;
		iStrPos += sText.length;
		elInput.focus();

		if (bSelStart)
		{
			elInput.selectionStart = iStrPos;
			elInput.selectionEnd = iStrPos;
		}
		else
		{
			oRange = document.selection.createRange();
			oRange.moveStart ('character', -elInput.value.length);
			oRange.moveStart ('character', strPos);
			oRange.moveEnd ('character', 0);
			oRange.select();
		}
		elInput.scrollTop = iScrollPos;
	},

	/**
	 * Measures the width of a scrollbar, in pixels. It's different on every
	 * browser of course, and may be further altered by the user's settings.
	 *
	 * This function adds a couple of divs to the page to measure them with and
	 * without the scrollbar.
	 *
	 * NOTE: I am assuming that the width of a vertical scrollbar is the same
	 * as the height of a horizontal one. If not, I'm sure it's not too hard to
	 * refactor this code to do the horizontal as well.
	 *
	 * @return {Number} the width of a scrollbar in pixels
	 */
	GetScrollbarWidth: function()
	{
		if (!L.isUndefined(iScrollbarWidth))
			return iScrollbarWidth;

		var elScroll = null, elInner = null, iNoScroll = 0, iScroll = 0;

		// Outer scrolling div
		elScroll = document.createElement('div');
		Dom.setStyle(elScroll, 'position', 'absolute');
		Dom.setStyle(elScroll, 'top', '-1000px');
		Dom.setStyle(elScroll, 'left', '-1000px');
		Dom.setStyle(elScroll, 'width', '100px');
		Dom.setStyle(elScroll, 'height', '50px');
		// Start with no scrollbar
		Dom.setStyle(elScroll, 'overflow', 'hidden');

		// Inner content div
		elInner = document.createElement('div');
		Dom.setStyle(elInner, 'width', '100%');
		Dom.setStyle(elInner, 'height', '200px');

		elScroll.appendChild(elInner);
		document.body.appendChild(elScroll);

		// Width of the inner div sans scrollbar
		iNoScroll = elInner.offsetWidth;

		// Width of the inner div with scrollbar
		Dom.setStyle(elScroll, 'overflow', 'scroll');
		iScroll = elInner.offsetWidth;
		if (iNoScroll == iScroll)
			iScroll = elScroll.clientWidth;

		document.body.removeChild(elScroll);
		iScrollbarWidth = (iNoScroll - iScroll);
		return iScrollbarWidth;
	},

	/**
	 * Displays an alert message to the user. If the YUI SimpleDialog control is loaded, then it uses that, otherwise falls back to a regular alert().
	 * NOTE: Unlike alert(), the YUI dialog will return control to the caller immediately. To perform an action once the user confirms, pass in an OKHandler.
	 *
	 * Attribs accepted:
	 * <dl>
	 * <dt> Icon:      </dt><dd> icon to use ALARM (default), BLOCK, WARN, HELP, INFO, and TIP.         </dd>
	 * <dt> OKHandler: </dt><dd> function to run once the user has confirmed.							</dd>
	 * <dt> Scope:     </dt><dd> scope to call the OKHandler with.										</dd>
	 * </dl>
	 *
	 * @param {String} sMessage alert message
	 * @param {Object} oAttribs extra attribs to control the alert. For backwards compatibility, if this is a function it's treated as an object with an "OKHandler" 
	 */
	Alert: function(sMessage, oParams)
	{
		if (L.isUndefined(oParams))
		{
			oParams = {};
		}
		else if (L.isFunction(oParams))
		{
			oParams = { OKHandler: oParams };
		}
		
		if (L.isUndefined(oParams.OKHandler))
		{
			oParams.OKHandler = function(){};
		}

		if (L.isUndefined(oParams.Scope))
		{
			oParams.Scope = window;
		}

		if (!L.isUndefined(YAHOO.widget.SimpleDialog))
		{
			var oPanelConfig,
				oPanel;
				
			oPanelConfig = {
				width: "300px",
				fixedcenter: "contained",
				visible: false,
				draggable: false,
				close: false,
				modal: true,
				text: sMessage,
				icon: YAHOO.widget.SimpleDialog.ICON_ALARM,
				constraintoviewport: true,
				buttons: [ { text: "OK", handler: function() { this.destroy(); oParams.OKHandler.call(oParams.Scope); }, isDefault: true } ]
			};

			if (!L.isUndefined(YAHOO.widget.SimpleDialog['ICON_' + oParams.Icon]))
				oPanelConfig.icon = YAHOO.widget.SimpleDialog['ICON_' + oParams.Icon];
			
			oPanel = new YAHOO.widget.SimpleDialog("alert" + Math.floor(Math.random() * 1000), oPanelConfig);
			oPanel.render(document.body);
			oPanel.show();
		}
		else
		{
			alert(sMessage);
			oParams.OKHandler.call(oParams.Scope);
		}
	},

	/**
	 * Displays a confirmation dialog to the user. If the YUI SimpleDialog control is loaded, then it uses that, otherwise falls back to a regular confirm().
	 * NOTE: Unlike confirm(), the YUI dialog will return control to the caller immediately. To perform an action once the user confirms, pass in an OKHandler.
	 * @param {String} sMessage message
	 * @param {Function} fnOKHandler handler to run if the user confirms
	 * @param {Function} fnCancelHandler handler to run if the user cancels
	 */
	Confirm: function(sMessage, oScope, fnOKHandler, fnCancelHandler)
	{
		if (L.isUndefined(fnOKHandler))
			fnOKHandler = function(){};
		if (L.isUndefined(fnCancelHandler))
			fnCancelHandler = function(){};

		if (!L.isUndefined(YAHOO.widget.SimpleDialog))
		{
			if (L.isUndefined(this.oConfirmPanel))
			{
				this.oConfirmPanel = new YAHOO.widget.SimpleDialog("alert",
				{
					width: "300px",
					fixedcenter: true,
					visible: false,
					draggable: false,
					close: false,
					modal: true,
					icon: YAHOO.widget.SimpleDialog.ICON_HELP,
					constraintoviewport: true
				});
				this.oConfirmPanel.render(document.body);
			}

			this.oConfirmPanel.cfg.setProperty('text', sMessage);
			this.oConfirmPanel.cfg.setProperty('buttons', [ 
				{ text: "Yes", handler: function() { this.hide(); fnOKHandler.call(oScope); }, isDefault: true },
				{ text: "No", handler: function() { this.hide(); fnCancelHandler.call(oScope); } } 
			]);
			this.oConfirmPanel.show();
		}
		else
		{
			if (confirm(sMessage))
				fnOKHandler();
			else
				fnCancelHandler();
		}
	},

	/**
	 * This object contains the default methods for the AJAX request "loading
	 * indicator". The default just puts a div in the top-right corner of the
	 * browser window.
	 *
	 * This is used for AjaxRequests which do not specify a LoadingIndicator.
	 *
	 * @property DefaultLoadingIndicator
	 */
	DefaultLoadingIndicator: {
		Show: function()
		{
			if (L.isUndefined(this.elLoadingIndicator))
			{
				this.elLoadingIndicator = document.createElement('div');
				this.elLoadingIndicator.id = 'ajaxloading';
				this.elLoadingIndicator.className = 'hide';
				this.elLoadingIndicator.appendChild(document.createTextNode(ZC.Util.GetText('Please wait…')));

				Dom.setStyle(this.elLoadingIndicator, 'opacity', 0);

				document.body.appendChild(this.elLoadingIndicator);

				if (YAHOO.env.ua.ie && YAHOO.env.ua.ie < 7)
				{
					// IE6 can't handle position: fixed 
					var fnAlignDiv = function() { 
						this.elLoadingIndicator.style.top = Dom.getClientRegion().top + 'px'; 
					}
					fnAlignDiv.call(this);

					Evt.on(window, 'resize', fnAlignDiv, this, true);
					Evt.on(window, 'scroll', fnAlignDiv, this, true);
				}
				Evt.on(window, 'unload', function() { this.elLoadingIndicator = null; }, this, true);
			}
			if (YAHOO.util.Anim)
			{
				var oAnim = new YAHOO.util.Anim(this.elLoadingIndicator, { opacity: { from: 0, to: 0.75 } }, 0.4);
				oAnim.onStart.subscribe(function() { Dom.removeClass(this.elLoadingIndicator, 'hide'); }, this, true);
				oAnim.animate();
			}
			else
			{
				Dom.removeClass(this.elLoadingIndicator, 'hide');
			}
		},
		Hide: function()
		{
			if (YAHOO.util.Anim)
			{
				var oAnim = new YAHOO.util.Anim(this.elLoadingIndicator, { opacity: { from: 0.75, to: 0 } }, 0.4);
				oAnim.onComplete.subscribe(function() { Dom.addClass(this.elLoadingIndicator, 'hide'); }, this, true);
				oAnim.animate();
			}
			else
			{
				Dom.addClass(this.elLoadingIndicator, 'hide');
			}
		}
	}
}

var U = ZC.Util, _GT = U.GetText;

/**
 * Generic Get/SetAttrib implementation, which we use to augment other objects
 * @class AttribProvider
 * @private
 */
var AttribProvider = function() {};
AttribProvider.prototype = {
	/**
	 * Gets the named attrib.
	 * @param {string} sName attrib to get
	 * @return Value attribute value
	 */
	GetAttrib: function(sName)
	{
		if (this.AttribIsset(sName))
		{
			return this.aDef[sName];
		}
		else
		{
			YAHOO.log('Attrib ' + sName + ' is not set', 'error', this.sName + '::GetAttrib');
		}
	},

	/**
	 * If the named attribute is set then its value is returned, else the default argument is returned.
	 * @param {string} sName attrib name to get
	 * @param {string{} mDefault value to return if attribute is not set. Defaults to false
	 * @return Value attribute value
	 */
	GetAttribDefault: function(sName, mDefault)
	{
		if(L.isUndefined(mDefault))
			mDefault = false;

		return this.AttribIsset(sName) ? this.GetAttrib(sName) : mDefault;
	},

	/**
	 * Checks that the named attrib is set
	 * @param {string} sName attrib to check
	 * @return {Boolean} true if the attrib is set
	 */
	AttribIsset: function(sName)
	{
		return !L.isUndefined(this.aDef[sName]);
	},

	/**
	 * Checks that the named attribs are set
	 * @param {Array} aAttribs attribs to check
	 * @return {Boolean} true if the attrib is set
	 */
	AttribsAreSet: function(aAttribs)
	{
		return U.Every(aAttribs, _AttribIsset, this);
	},

	/**
	 * Sets the value of an attribute. 
	 *
	 * If the object needs to react to an attribute change, then define a
	 * method <code>AttribMethod_<em>AttribName</em></code>. This method will
	 * be called with the new attrib value when the attrib value is changed.
	 *
	 * @param {String} sName attrib name to set
	 * @param Value new attrib value
	 */
	SetAttrib: function(sName, Value)
	{
		var OldValue = this.aDef[sName];
		this.aDef[sName] = Value;

		if (OldValue != Value && L.isFunction(this['AttribMethod_' + sName]))
		{
			this['AttribMethod_' + sName](Value, OldValue);
		}
	},

	/**
	 * Sets the value of many attributes.
	 * @param {Object} oAttribs hash of attrib name => attrib value
	 */
	SetAttribs: function(oAttribs)
	{
		U.ForEach(oAttribs, function(Value, sName)
		{
			this.SetAttrib(sName, Value);
		}, this);
	},

	/**
	 * Initial setup for all classes using this provider. Runs any available
	 * AttribMethods for the initial attrib values.
	 *
	 * Call this from the end of your constructor when using this provider.
	 */
	AttribProviderSetup: function()
	{
		this._bAttribProviderSetup = true;
		
		U.ForEach(this.aDef, function(Value, sKey)
		{
			if (L.isFunction(this['AttribMethod_' + sKey]))
			{
				this['AttribMethod_' + sKey](Value);
			}
		}, this);

		this._bAttribProviderSetup = false;
	}
}

/**
 * Generic GetWidget implementation, which we use to augment other objects
 * @class GetWidgetProvider
 * @private
 */
var GetWidgetProvider = function() {};
GetWidgetProvider.prototype = {
	/**
	 * Retrieves a widget by name.
	 *
	 * sSearch may be a dot-separated list of widget names (e.g. "FrmAddEdit.Enabled"), in which case the Enabled widget would be
	 * retrieved from the FrmAddEdit widget. sSearch may also be a single widget name, in which case a depth-first search of the widget tree is
	 * performed, and the first match returned.
	 *
	 * @param {String} sSearch widget name
	 * @return {Object} widget object, or <em>undefined</em> if the widget can't be found
	 */
	GetWidget: function(sSearch)
	{
		var iNumSearchObjects, iDotPos, i, oSearchObject, sSearchPart, sSearchRest, sName, oChildSearch;

		if (L.isObject(sSearch))
		{
			if (sSearch instanceof ZC.Core.Widget)
			{
				return sSearch;
			}
			else if (!L.isUndefined(sSearch.name)) // HTML INPUT element?
			{
				sSearch = sSearch.name;
			}
			else if (!L.isUndefined(sSearch.id)) // some other HTML element?
			{
				sSearch = sSearch.id;
			}
			else
			{
				sSearch = sSearch.toString();
			}
		}

		iNumSearchObjects = this._aSearchObjects.length;
		iDotPos = sSearch.indexOf('.');

		if (iDotPos > -1)
		{
			sSearchPart = sSearch.substr(0, iDotPos);
			sSearchRest = sSearch.substr(iDotPos + 1);

			for (i = 0; i < iNumSearchObjects; ++i)
			{
				oSearchObject = this[this._aSearchObjects[i]];
				if (!L.isUndefined(oSearchObject[sSearchPart]))
				return oSearchObject[sSearchPart].GetWidget(sSearchRest);
			}

			return undefined;
		}

		for (i = 0; i < iNumSearchObjects; ++i)
		{
			oSearchObject = this[this._aSearchObjects[i]];
			if (!L.isUndefined(oSearchObject[sSearch]))
			return oSearchObject[sSearch];

			// descend down the tree looking for the widget
			for (sName in oSearchObject)
			{
				if (L.hasOwnProperty(oSearchObject, sName) && L.isObject(oSearchObject[sName]))
				{
					oChildSearch = oSearchObject[sName].GetWidget(sSearch);
					if (!L.isUndefined(oChildSearch))
						return oChildSearch;
				}
			}
		}

		return undefined;
	},

	/**
	 * Retrieves an array of widgetss that pass the test applied by supplied boolean method
	 * @param {Function} fnMethod A boolean method for testing blocks, the only argument recieved is the block itself.
	 * @param {Object} oScope (optional) scope override for fnMethod
	 * @return {Array} array of blocks.
	 */
	GetWidgetsBy: function(fnMethod, oScope)
	{
		var iNumSearchObjects, i, oSearchObject, sSearchPart, sSearchRest, sName, oChildSearch, aResult = [];

		iNumSearchObjects = this._aSearchObjects.length;

		for (i = 0; i < iNumSearchObjects; ++i)
		{
			oSearchObject = this[this._aSearchObjects[i]];
			U.ForEach(oSearchObject, function(oSearch)
			{
				if (fnMethod.call(oScope, oSearch))
				aResult.push(oSearch);
			});

			// descend down the tree looking for the widget
			for (sName in oSearchObject)
			{
				if (L.hasOwnProperty(oSearchObject, sName))
				{
					aChildSearch = oSearchObject[sName].GetWidgetsBy(fnMethod, oScope);
					aResult.push.apply(aResult, aChildSearch);
				}
			}
		}

		return aResult;
	}
};

/**
 * Generic implementation of events and eventlisteners, which we use to augment other objects
 * @class EventProvider
 * @private
 */
var EventProvider = function() { }
EventProvider.prototype = {
	/**
	 * Destructor. Should be called by the Destruct methods of objects using EventProvider.
	 */
	EventProviderDestruct: function()
	{
		if (!L.isUndefined(this.aEventListeners))
		{
			U.ForEach(this.aEventListeners, function(aELs) { 
				U.ForEach(aELs, function (oEventListener) { 
					try { oEventListener.Destruct(); } 
					catch (e) { YAHOO.log("Error during destruction: " + e.message + " (" + e.fileName + ":" + e.lineNumber + ")", "warn", this.sName); }
				});
			});
		}
		if (!L.isUndefined(this.aEvents) && !L.isUndefined(this.aEvents['load']))
		{
			U.ForEach(this.aEvents['load'], function(oLoadEvent) {
				if (!L.isUndefined(oLoadEvent.oEvent))
				{
					oLoadEvent.oEvent.subscribeEvent.unsubscribe(this._SubscribeEventHandler);
				}
			}, this);
		}
		this.aEventListeners = {};
		this.aEvents = {};
	},

	// these two properties are created when first required.. adding them to
	// this prototype would cause them to be shared amongst the objects that
	// use this class, rather than created anew for each one.
	
	/**
	 * Array of event listeners attached to this widget
	 * @property aEventListeners
	 * @type Object
	 * @private
	 */

	/**
	 * Array of events attached to this widget
	 * @property aEvents
	 * @type Object
	 * @private
	 */

	/**
	 * Adds event handlers to this widget that fire the custom event sEventName.
	 *
	 * If the event name contains "WIDGET", "FORM" or "BLOCK" then these will be
	 * replaced with the form+widget name, form name or block name, making it
	 * easier to create events that are specific to a widget, form or block.
	 *
	 * @param {String / Function / Object} EventNameOrFunction Name of the custom event to fire, or a function to call, or a CustomEvent object.
	 * @param {Array} aOn Array of events to attach the event to. May also be a string for attaching a single event.
	 * @param {Object} oScope (optional) scope override for the event handler
	 * @param {Object} oObjectOverride (internal use) the object to pass to the event handler
	 * @return {Object} the custom event object
	 */
	AddEvent: function(EventNameOrFunction, aOn, oScope, oObjectOverride)
	{
		var fnFireEvent, oEvent,
			elEvent = this.GetEventElement();

		if (!elEvent && (L.isUndefined(this.HasChildWidgets) || !this.HasChildWidgets()))
		{
			YAHOO.log('this.GetEventElement() is not valid, no child widgets and AddEvent is not overridden', "error", this.sName + '#AddEvent');
			return undefined;
		}

		if (L.isUndefined(oObjectOverride))
			oObjectOverride = this;

		if (!(this instanceof ZC.Core.Form) && !L.isUndefined(this.aChildWidgets))
			U.ForEach(this.aChildWidgets, function(oWidget) { oWidget.AddEvent(EventNameOrFunction, aOn, oScope, oObjectOverride) }, this);

		if (!elEvent)
			return;

		if (!L.isArray(aOn))
			aOn = [aOn];

		if (typeof EventNameOrFunction == 'function')
		{
			fnFireEvent = function (oEvent, oObj) { return oObj.EventNameOrFunction.call(this, oEvent, oObj.Obj); }
		}
		else
		{
			if (EventNameOrFunction instanceof YAHOO.util.CustomEvent)
			{
				oEvent = EventNameOrFunction;
			}
			else
			{
				EventNameOrFunction = this._InternalEventName(EventNameOrFunction);
				oEvent = ZC.JSManager.GetEvent(EventNameOrFunction);
			}

			fnFireEvent = function(oSrcEvent, oObj) { 
				oObj.Event.fire(oObj.Override, oSrcEvent); 
			};
		}

		U.ForEach(aOn, function(sEvent)
		{
			if (L.isUndefined(this.aEvents))
				this.aEvents = {};
			if (L.isUndefined(this.aEvents[sEvent]))
				this.aEvents[sEvent] = [];

			this.aEvents[sEvent].push({ fnFire: fnFireEvent, oEvent: oEvent });
			var oObj = { 
				Obj: this, Event: oEvent, EventNameOrFunction: EventNameOrFunction, Override: oObjectOverride 
			};

			// special case for load event. we run this from the AfterManagerInit event once everything's been initialised (even stuff that inits in ManagerInit).
			// it's also cleared, then rerun during a AJAX update
			if (sEvent == 'load')
			{
				ZC.JSManager.GetEvent('OnLoadEvent').subscribe(function(oEvent, a, oObj)
				{
					fnFireEvent.call(oObj.Scope, null, oObj);
					if (!L.isUndefined(oObj.Event))
					{
						// if we've been passed a custom event, subscribe to that event's subscribeEvent so that we can 
						// run eventlisteners that subscribe later expecting to be run on-"load"
						oObj.Event.subscribeEvent.subscribe(this._SubscribeEventHandler, oObj.Override, this);
					}
				}, oObj, this);
			}
			else
			{
				var aHandlers = Evt.getListeners(this._elInput, sEvent) || [], i, iMax;
				for (i = 0, iMax = aHandlers.length; i < iMax; ++i)
				{
					if (((oScope && aHandlers[i].scope == oScope) || (!oScope && aHandlers[i].scope == this))
							&& aHandlers[i].obj.EventNameOrFunction == EventNameOrFunction)
					{
						// already listening for this event, next!
						return;
					}
				}

				if (!Evt.addListener(elEvent, sEvent, fnFireEvent, oObj, oScope || this))
					YAHOO.log('addListener returned false for ' + sEvent, "warn", this.sName + "#AddEvent");
			}
		}, this);

		return oEvent;
	},

	/**
	 * Adds a number of events given a definition
	 * @param {Object} aDef definition: EventName => [ array of events ]
	 */
	AddEvents: function(aDef)
	{
		U.ForEach(aDef, function(aOn, sEventName) { this.AddEvent(sEventName, aOn); }, this);
	},

	/**
	 * When a custom event is added to this widget to fire on "load", we
	 * subscribe to the subscribeEvent (fired when new objects subscribe to
	 * that event) so that we can immediately fire the event handlers of any
	 * eventlisteners that come along later.
	 * @param {String} sType the type of event that just fired (subscribeEvent)
	 * @param {Array} aArgs the arguments passed to the subscribe method (0 = event handler, 1 = custom object, 2 = scope override)
	 * @param {Object} oObj the widget override passed into AddEvent, or the current widget if none
	 * @private
	 */
	_SubscribeEventHandler: function(sType, aArgs, oObj)
	{
		var fnEventHandler = aArgs[0],
		    aEHArgs = [oObj, aArgs[1]],
		    oOverride = aArgs[2];

		if (oOverride === true)
			oOverride = aArgs[1];

		fnEventHandler.call(oOverride, oEvent.type, aEHArgs);
	},

	/**
	 * Instantiates an EventListener, attached to the current widget, and subscribes to
	 * the named event.
	 * @param {String} sEventName Name of the custom event to subscribe to
	 * @param {Array} aDef The definition for the eventlistener
	 * @return {Object} The new event listener
	 */
	AddEventListener: function(sEventName, aDef)
	{
		sEventName = this._InternalEventName(sEventName);
		var oEvent = ZC.JSManager.GetEvent(sEventName), i, iMax, oEL,
		    fnEventListenerConstructor = ZC.JSManager.GetComponent(aDef.EventListenerType, aDef.Module || 'Core', 'EventListener');

		YAHOO.log("Adding event listener for " + sEventName + " (" + this._InternalEventName(sEventName) + ") to " + this.sName, "debug", "AddEventListener");
		if (fnEventListenerConstructor)
		{
			if (L.isUndefined(this.aEventListeners))
			{
				this.aEventListeners = { };
			}

			if (L.isUndefined(this.aEventListeners[sEventName]))
			{
				this.aEventListeners[sEventName] = [];
			}
			else
			{
				for (i = 0, iMax = this.aEventListeners[sEventName].length; i < iMax; ++i)
				{
					if (U.ObjectsEqual(this.aEventListeners[sEventName][i].aDef, aDef))
					{
						YAHOO.log("Listener already exists, not adding another.", "debug", "AddEventListener");
						return this.aEventListeners[sEventName][i];
					}
				}
			}

			oEL = new fnEventListenerConstructor(this, oEvent, aDef);
			this.aEventListeners[sEventName].push(oEL);
			return oEL;
		}
		else
		{
			throw new Error("Unable to load event listener: " + (aDef.Module || 'Core') + "/" + aDef.EventListenerType);
		}
	},

	/**
	 * Adds a number of eventlisteners given a definition
	 * @param {Object} aDef event listener definition: EventName => [ array of event listener definitions ]
	 */
	AddEventListeners: function(aDef)
	{
		U.ForEach(aDef, function(aELDefs, sEventName)
		{
			U.ForEach(aELDefs, function(aELDef) { 
				try
				{
					this.AddEventListener(sEventName, aELDef) 
				}
				catch (ex)
				{
					YAHOO.log('Unable to add event listener ' + aELDef.EventListenerType + ' for event ' 
						+ sEventName + ' to ' + this.sName + ': ' + ex.message + " (" + ex.fileName + ":" + ex.lineNumber + ")", 'error', this.sName);
				}
			}, this);
		}, this);
	},

	/**
	 * Turn an event name from a def into our internal event name, substituting WIDGET, FORM and BLOCK tags where present.
	 *
	 * WIDGET is replaced with this widget's unique ID. FORM is replaced with the form name. BLOCK is replaced with the block's UniqueID.
	 *
	 * Subclasses may override this to provide other tags if necessary, but
	 * should call their parent on the value as well (even if you've replaced
	 * something, there may be use cases for having two tags in an event name.
	 *
	 * @param {String} External event name
	 * @return {String} Internal event name
	 * @protected
	 */
	_InternalEventName: function(sEventName)
	{
		if (this instanceof ZC.Core.Block)
			return sEventName.replace('BLOCK', this.UniqueID());

		if (this instanceof ZC.Core.Widget)
		{
			var oBlock = this.FindContainingBlock();
			return sEventName.replace('WIDGET', this._WidgetNameToID())
							 .replace('FORM', this.oForm ? this.oForm.sName : 'NoForm')
							 .replace('BLOCK', oBlock ? oBlock.sName : 'NoBlock');
		}
	},

	/**
	 * Checks this object's elements for event handlers for the given event and fires any that are registered.
	 * @param {String} sEvent Event name
	 * @protected
	 */
	_FireEventHandlers: function(sEvent)
	{
		var elEvent = this.GetEventElement(),
			oDOMEvent,
			aHandlers,
			aEventArgs = [], i, iMax;

		if (L.isArray(elEvent))
			elEvent = elEvent[0];

		aHandlers = Evt.getListeners(elEvent, sEvent);
		if (aHandlers)
		{
			for (i = 1, iMax = arguments.length; i < iMax; i++)
				aEventArgs.push(arguments[i]);

			if (aEventArgs.length && !L.isUndefined(aEventArgs[0].clientY)) 
				oDOMEvent = aEventArgs.shift();

			U.ForEach(aHandlers, function(aHandler)
			{
				var oScope = aHandler.scope,
				    fnHandler = aHandler.fn,
					oArg = aHandler.obj || oScope,
					mEvent = oDOMEvent || aHandler.type;

				fnHandler.call(oScope, mEvent, oArg, aEventArgs);
			});
		}
	}
};

ZC.Namespace('Core');

/**
 * The manager object, responsible for initialising all the JS widgets and events on the page.
 * @class JSManager
 * @namespace ZC
 */
ZC.JSManager = {
	/**
	 * Collection of all of the client-side objects created for this page. Use GetCSO to fetch CSO objects.
	 *
	 * @private
	 * @type Object
	 * @property aClientSideObjects
	 */
	aClientSideObjects: {},

	/**
	 * Collection of all of the custom events defined in this page. Use GetEvent to fetch event objects.
	 *
	 * @private
	 * @type Object
	 * @property aEvents
	 */
	aEvents: {},

	/**
	 * Collection of all of the forms on this page. Use the GetWidget method to retrieve form objects.
	 *
	 * @private
	 * @type Object
	 * @property aForms
	 */
	aForms: {},

	/**
	 * All of the top-level blocks for this page. Use the GetWidget or GetBlock* methods to search the block tree.
	 *
	 * @private
	 * @type Object
	 * @property aBlocks
	 */
	aBlocks: {},

	/**
	 * All of the singleton Validator objects. Use GetValidator to access this.
	 *
	 * @private
	 * @type Object
	 * @property aValidators
	 */
	aValidators: {},

	/**
	 * All of the singleton FormValidator objects. Use GetFormValidator to access this.
	 *
	 * @private
	 * @type Object
	 * @property aFormValidators
	 */
	aFormValidators: {},

	/**
	 * This method gets called from the HTML, with the configuration data
	 * required for the widgets on the page. It is responsible for
	 * instantiating all the other required objects and wiring up events.
	 *
	 * This method fires the custom event 'ManagerInit' once all the widgets on
	 * the page have been set up.
	 *
	 * @param {Object} oConfig
	 */
	Init: function(oConfig, bIframeInit)
	{
		/*		
		 *	Should be fixed in YUI 2.8.0, so this code no longer necessary?
		 *
		if (!bIframeInit && window.frameElement && YAHOO.env.ua.ie)
		{
			// IE chucks out an "operation aborted" error if we try to init in the onDOMReady event in an iframe.
			// if we are loaded in an iframe, run our initialisation on load instead. I think most of the time we use iframes they are hidden
			// on load anyway, so a delay in their init shouldn't matter
			Evt.on(window, 'load', function() { this.Init(oConfig, true); }, this, true);
			return;
		}*/

		// add 'yui-pe' class to the document element. This allows the
		// yui-pe-content class to be added to HTML that is going to be
		// progressively enhanced, which hides it to avoid a flash of
		// un-enhanced content.
	    document.documentElement.className = "yui-pe"; 

		this.oConfig = oConfig;
		if (this.oConfig.DEBUG)
		{
			Evt.throwErrors = true;
			if (YAHOO.widget.Logger)
			{
				if (window.console && console.log)
					YAHOO.widget.Logger.enableBrowserConsole();
				else
				{
					Evt.onDOMReady(function() {
						var myContainer = document.createElement("div");
						document.body.appendChild(myContainer);
						this.oYUILogReader = new YAHOO.widget.LogReader(myContainer, { width: '800px' });
					}, null, this);
				}
			}
		}
		//YAHOO.log("Initialising...", "debug", "ZC.JSManager#Init");

		Evt.onDOMReady(function()
		{
			//YAHOO.log('onDOMReady fired', 'debug', 'ZC.JSManager#Init');

			// Add 'yui-skin-sam' classname to body tag
			Dom.addClass(document.body, 'yui-skin-sam');
			this.oOverlayManager = new YAHOO.widget.OverlayManager();

			if (!L.isUndefined(this.oConfig.ClientSideObjects))
			{
				//YAHOO.log("Started initialising CSOs", "debug", "ZC.JSManager#Init");
				U.ForEach(this.oConfig.ClientSideObjects, function(aDef, sName)
				{
					var fnConstructor = ZC.JSManager.GetComponent(aDef.Type, aDef.Module || 'Core', 'ClientSideObject');
					if (fnConstructor)
						this.aClientSideObjects[sName] = new fnConstructor(aDef);
				}, this);
				//YAHOO.log("Finished initialising CSOs", "debug", "ZC.JSManager#Init");
			}
			if (!L.isUndefined(this.oConfig.Blocks))
			{
				//YAHOO.log("Started initialising Blocks", "debug", "ZC.JSManager#Init");
				U.ForEach(this.oConfig.Blocks, function(aDef, sName)
				{
					this.aBlocks[sName] = ZC.Core.Block.NewFromDef(sName, aDef);
				}, this);
				//YAHOO.log("Finished initialising Blocks", "debug", "ZC.JSManager#Init");
			}
			if (!L.isUndefined(this.oConfig.Forms))
			{
				//YAHOO.log("Started initialising Forms", "debug", "ZC.JSManager#Init");
				U.ForEach(this.oConfig.Forms, function(aDef, sName)
				{
					this.RegisterForm(new ZC.Core.Form(sName, aDef, this));
				}, this);
				//YAHOO.log("Finished initialising Forms", "debug", "ZC.JSManager#Init");
			}

			/**
			 * Custom event that is fired when the manager has finished initialising the elements on the page
			 * @event ManagerInit
			 * @public
			 * @memberof ZC.JSManager
			 */
			var oInitEvent = this.GetEvent('ManagerInit');
			oInitEvent.fireOnce = true;
			oInitEvent.subscribe(function() { this.AutoTooltips(); }, this, true);
			oInitEvent.subscribe(this._AutoPopups, this, true);

			//YAHOO.log('Firing ManagerInit event', 'debug', 'ZC.JSManager#Init');
			oInitEvent.fire();

			var oAfterInitEvent = this.GetEvent('AfterManagerInit');
			oAfterInitEvent.fireOnce = true;
			oAfterInitEvent.fire();

			this.GetEvent('OnLoadEvent').fire();

			if (YAHOO.env.ua.ie)
			{
				// IE has some bizarre rendering bugs which are fixed by forcing a repaint:
				document.body.style.display = 'none';
				document.body.style.display = '';
			}

			document.documentElement.className = ""; 
		}, null, this);

		Evt.on(window, 'unload', function() 
		{
			/*
			 * Clean up everything when the page is unloaded, required due to memory leaks in IE and FF (possibly others too).
			 * IE6 is worst affected of course..
			 */
			U.ForEach(this.aBlocks, function(oBlock) { 
				try { oBlock.Destruct(); } 
				catch (e) { YAHOO.log("Error during destruction: " + e.message + " (" + e.fileName + ":" + e.lineNumber + ")", "warn", this.sName); }
			});
			U.ForEach(this.aClientSideObjects, function(oCSO) { 
				try { oCSO.Destruct(); } 
				catch (e) { YAHOO.log("Error during destruction: " + e.message + " (" + e.fileName + ":" + e.lineNumber + ")", "warn", this.sName); }
			});

			if (this.oOverlayManager)
			{
				var aOverlays = this.oOverlayManager.cfg.getProperty('overlays');
				if (aOverlays)
				{
					U.ForEach(aOverlays, function(oOverlay)
					{
						oOverlay.destroy();
						oOverlay = null;
					});
					aOverlays = null;
				}
				this.oOverlayManager = null;
			}

			this.aClientSideObjects = {};
			this.aEvents = {};
			this.aForms = {};
			this.aBlocks = {};
			this.aValidators = {};
			this.aFormValidators = {};
			this.oAutoTooltips = null;
			this.oYUILogReader = null;

			ZC.JSManager = null;
			ZC = null;
		}, this, true);
	},

	/**
	 * Automatically attaches the YUI Tooltip widget to elements on the page
	 * that have the classname of 'tooltip'. We check that the Tooltip widget
	 * is loaded, and do nothing if not.
	 * @param [{HTMLElement}] optional element to scan for new tooltips
	 */
	AutoTooltips: function(elScan)
	{
		var aTooltips, oTTCfg, aContext;
		
		if (YAHOO.env.getVersion('container'))
		{
			aTooltips = Dom.getElementsByClassName('tooltip', undefined, elScan);
			if (aTooltips.length)
			{
				if (L.isUndefined(this.oAutoTooltips))
				{
					oTTCfg = { context: aTooltips, autofillheight: false, autodismissdelay: 120000, zIndex: 45 };
					if (!L.isUndefined(YAHOO.util.Anim))
						oTTCfg.effect = { effect: YAHOO.widget.ContainerEffect.FADE, duration: 0.25 };
					this.oAutoTooltips = new YAHOO.widget.Tooltip('zcb_auto_tt', oTTCfg);
					this.oOverlayManager.register(this.oAutoTooltips);
					this.oAutoTooltips.beforeShowEvent.subscribe(function() { this.oOverlayManager.bringToTop(this.oAutoTooltips); }, this, true);
				}
				else
				{
					aContext = this.oAutoTooltips.cfg.getProperty('context');
					aContext.push.apply(aContext, aTooltips);
					this.oAutoTooltips.cfg.setProperty('context', aContext);
				}
			}
		}
	},

	/**
	 * This method automatically attaches click handlers to any links with a
	 * class of 'popup', that open up a popup to load the target of the link.
	 * The width  and height can be set by adding a class name of the form
	 * 'w<width>h<height>' (e.g. 'w300h300' for a 300x300 window)
	 *
	 * TODO: It would be nice to use a YUI Panel instead. That requires some
	 * changes to the server-side code, so that we can request a URL via AJAX
	 * and receive the data in a suitable format (probably JSON, with extra
	 * data like css/scripts that need loading, dialog title, etc).
	 *
	 * @private
	 */
	_AutoPopups: function()
	{
		var fnOpenPopup = function(elLink, sURL, iWidth, iHeight)
		{
			var sOptions = 'status=1, resizable=1, scrollbars=yes';
			if (!L.isUndefined(iWidth))
				sOptions += ', width = ' + iWidth;
			if (!L.isUndefined(iHeight))
				sOptions += ', height = ' + iHeight;

			window.open(sURL, '_blank', sOptions);
		}

		var aPopups = Dom.getElementsByClassName('popup');
		if (aPopups.length)
		{
			Evt.on('click', function(oEvent)
			{
				var elLink = Evt.getTarget(oEvent);
				var aMatches = elLink.className.match(/(w(\d+))?(h(\d+))?/);
				var iWidth = aMatches[2];
				var iHeight = aMatches[4];
				fnOpenPopup(elLink, elLink.href, iWidth, iHeight);
			});
		}
	},

	/**
	 * Registers a form with the manager
	 * @param {Object} oForm form widget to register
	 */
	RegisterForm: function(oForm)
	{
		this.aForms[oForm.sName] = oForm;
		U.ForEach(oForm.aChildWidgets, function(oWidget) { oWidget.oForm = oForm; });
	},

	/**
	 * Takes a module, type and subtype and returns the required class
	 * @param {String} sCmpName component name, may contain module & type
	 * @param {String} sModule module name
	 * @param {String} sType object type (Block, Widget, EventListener, ...)
	 * @return {Object} the required object constructor
	 */
	GetComponent: function(sCmpName, sModule, sType)
	{
		var sFirstPart, sSecondPart, iFirstPos, iSecondPos, aTry = [], i, iMax;

		if (L.isUndefined(sCmpName))
			return false;

		iFirstPos = sCmpName.indexOf('_');
		if(iFirstPos != -1)
			sFirstPart = sCmpName.substring(0, iFirstPos);

		iSecondPos = sCmpName.indexOf('_', iFirstPos + 1);
		if (iSecondPos != -1)
			sSecondPart = sCmpName.substring(iFirstPos + 1, iSecondPos);

		if (sFirstPart && sSecondPart) // could be module_type_subtype
			aTry.push([sFirstPart, sSecondPart, sCmpName.substring(iSecondPos + 1)]);

		if (sFirstPart)
		{
		   	// could be type_subtype and separate module
			aTry.push([sModule || 'Core', sFirstPart, sCmpName.substring(iFirstPos + 1)]);

			// could be module_type
			aTry.push([sFirstPart, sCmpName.substring(iFirstPos + 1), undefined]);
		}

		aTry.push([sModule || 'Core', sType, sCmpName]);

		for (i = 0, iMax = aTry.length; i < iMax; i++)
		{
			var sM = aTry[i][0];
			var sT = aTry[i][1];
			var sST = aTry[i][2];

			if (L.isUndefined(ZC[sM]))
			{
				//YAHOO.log("Module '" + sM + "' not loaded", "debug", "ZC.JSManager#GetComponent");
				continue;
			}
			if (L.isUndefined(ZC[sM][sT]))
			{
				//YAHOO.log("Object type '" + sT + "' in module '" + sM + "' not loaded", "debug", "ZC.JSManager#GetComponent");
				continue;
			}

			if (L.isUndefined(sST))
			{
				// no subtype
				return ZC[sM][sT];
			}

			if (L.isUndefined(ZC[sM][sT][sST]))
			{
				//YAHOO.log("Object '" + sST + "' (type '" + sT + "') in module '" + sM + "': not loaded", "debug", "ZC.JSManager#GetComponent");
				continue;
			}

			//YAHOO.log("Loaded object ZC." + [sM, sT, sST].join('.'), "debug", "ZC.JSManager#GetComponent");
			return ZC[sM][sT][sST];
		}

		YAHOO.log("Unable to load component (" + [sCmpName, sModule, sType].join(", ") + ")", "warn", "ZC.JSManager#GetComponent");
		return undefined;
	},

	/**
	 * Loads a YUI component with YUI Loader
	 * @param {Object} oConfig config object in the format accepted by YUILoader
	 */
	LoadYUILib: function(oConfig)
	{
		var oYUILoader = this.GetCSO('YUILoader'), fnConstructor;
		if (L.isUndefined(oYUILoader))
		{
			fnConstructor = ZC.JSManager.GetComponent('YUILoader', 'Core', 'ClientSideObject');
			if (!fnConstructor)
			{
				YAHOO.log('Unable to locate YUILoader CSO', 'error', 'ZC.JSManager');
				return false;
			}
			
			this.aClientSideObjects['YUILoader'] = new fnConstructor(aDef);
		}

		oYUILoader.LoadYUILib(oConfig);
	},

	/**
	 * Retrieves a block by UniqueID.
	 * @param {String} sUniqueID to locate
	 * @return {Object} A block object, or <em>undefined</em> if not found
	 */
	GetBlockByUniqueID: function(sUniqueID)
	{
		for (var sName in this.aBlocks)
		{
			if (L.hasOwnProperty(this.aBlocks, sName))
			{
				if (this.aBlocks[sName].UniqueID() == sUniqueID)
					return this.aBlocks[sName];

				var oChildBlock = this.aBlocks[sName].GetBlockByUniqueID(sUniqueID);
				if (oChildBlock)
					return oChildBlock;
			}
		}
		return undefined;
	},

	/**
	 * Retrieves an array of blocks that pass the test applied by supplied boolean method
	 * For optimised performance supply a blockname, only its children will be tested
	 * @param {Function} fnMethod A boolean method for testing blocks, the only argument recieved is the block itself.
	 * @param {String | Object} mRoot (optional) A Block ID or a block to use as a starting point.
	 * @param {Object} oScope (optional) scope override for mRoot
	 * @return {Array} array of blocks.
	 */
	GetBlocksBy: function(fnMethod, mRoot, oScope)
	{
		var aBlocks = [], aRoot = false;
		if(!L.isUndefined(mRoot))
		{
			if(L.isString(mRoot))
				mRoot = this.GetBlockByUniqueID(mRoot);

			if(L.isObject(mRoot))
				aRoot = mRoot.aChildBlocks;
		}
		else
		{
			aRoot = this.aBlocks;
		}

		if(!aRoot)
			return [];

		ZC.Util.ForEach(aRoot, function(oBlock)
		{
			if(fnMethod.call(oScope, oBlock))
				aBlocks.push(oBlock);

			// Recurse through child blocks.
			aBlocks = aBlocks.concat(ZC.JSManager.GetBlocksBy(fnMethod, oBlock, oScope));
		});

		return aBlocks;
	},

	/** 
	 * @private 
	 */
	_aSearchObjects: [ 'aForms', 'aBlocks' ],

	/**
	 * Retrieves a CSO by name
	 * @param {String} sSearch CSO name
	 * @return {Object} the client-side object
	 */
	GetCSO: function(sSearch)
	{
		return this.aClientSideObjects[sSearch];
	},

	/**
	 * Retrieves an Event by name. Creates a new event object if no event exists with this name already.
	 * @param {String} sName Event name
	 * @return {Object} an event object
	 */
	GetEvent: function(sName)
	{
		if (L.isUndefined(this.aEvents[sName]))
			this.aEvents[sName] = new YAHOO.util.CustomEvent(sName);

		return this.aEvents[sName];
	},

	/**
	 * Gets a validator object
	 * @param {String} sName validator class name, either just the name of a validator in the Core_Validator_* namespace, or a full class name
	 * @return {Object} a validator object
	 */
	GetValidator: function(sName)
	{
		if (L.isUndefined(this.oValidatorRegex))
			this.oValidatorRegex = /^(.*)_Validator_(.*)$/;

		return this._GetValidator(sName, this.aValidators, this.oValidatorRegex);
	},

	/**
	 * Gets a form validator object
	 * @param {String} sName validator class name, either just the name of a validator in the Core_FormValidator_* namespace, or a full class name
	 * @return {Object} a form validator object
	 */
	GetFormValidator: function(sName)
	{
		if (L.isUndefined(this.oFormValidatorRegex))
			this.oFormValidatorRegex = /^(.*)_FormValidator_(.*)$/;

		return this._GetValidator(sName, this.aFormValidators, this.oFormValidatorRegex);
	},

	/**
	 * Helper function implementing the common code for Get(Form)Validator#
	 * @private
	 * @param {String} sName validator class name, either just the name of a validator in the Core_FormValidator_* namespace, or a full class name
	 * @param {Object} the array of existing validators
	 * @param {RegExp} the regular expression to match validator class names
	 * @return {Object} a validator object
	 */
	_GetValidator: function(sName, aValidators, oValidatorRegex)
	{
		if (!L.isUndefined(aValidators[sName]))
			return aValidators[sName];

		var aMatches = oValidatorRegex.exec(sName);
		if (!aMatches)
		{
			if (L.isUndefined(ZC.Core.Validator[sName]))
			{
				//YAHOO.log("unable to find validator '" + sName + "'", "info", "ZC.JSManager#GetValidator");
				return undefined;
			}
			return (aValidators[sName] = new ZC.Core.Validator[sName]());
		}
		else
		{
			if (L.isUndefined(ZC[aMatches[1]]) || L.isUndefined(ZC[aMatches[1]].Validator) || L.isUndefined(ZC[aMatches[1]].Validator[aMatches[2]]))
			{
				//YAHOO.log("unable to find validator '" + sName + "'", "info", "ZC.JSManager#GetValidator");
				return undefined;
			}
			return (aValidators[sName] = new ZC[aMatches[1]].Validator[aMatches[2]]());
		}
	},

	/**
	 * Constructs a URL from an array of parameters.
	 *
	 * The key of each item in <code>oParams</code> is the querystring variable name. 
	 * The value can either be a string or a widget (in fact, it can be any object 
	 * which defines a <code>GetValue</code> method).
	 *
	 * sURL may have a query string already present, in which case this is
	 * parsed and added to oParams. Values in oParams override anything
	 * specified in the query string of sURL. The '_ts' value from sURL's query
	 * string is always ignored, and the new state (passed in the JSON config
	 * array) will be used instead, unless it's overridden in oParams.
	 *
	 * @param {Object/String} oParams Query string parameters. 
	 * @param {String} [sURL] Optional URL. Uses the current location (including its query string) if not specified
	 * @return {String} a URL
	 */
	URL: function(oParams, sURL)
	{
		var iQSPos, fnURLArray, aParamParts;

		oParams = oParams || {};
		sURL = sURL || window.location.href.replace(/(\?|#).*/,'');

		if (L.isString(oParams))
		{
			// split param string
			oParams = this.ParseQueryString(oParams);
		}

		// use the state value we've received in the JSON config array, unless the caller has specified a state in oParams
		if (this.oConfig.StateID && L.isUndefined(oParams['_ts']))
			oParams['_ts'] = this.oConfig.StateID;

		if (sURL.lastIndexOf('/') == (sURL.length - 1))
			sURL += 'index';

		iQSPos = sURL.indexOf('?');
		if (iQSPos > -1)
		{
			// values specified in oParams take precedence over any present in the URL
			oParams = L.merge(this.ParseQueryString(sURL.substr(iQSPos + 1)), oParams);
			sURL = sURL.substr(0, iQSPos);
		}

		fnEncodeURLParams	= function(mVar, sArg)
		{
			if(mVar.length == 0)
				return '';
			
			var aParamParts = [];
			U.ForEach(mVar, function(Value, Key)
			{
				// values can be widgets
				if (L.isObject(Value) && Value.GetValue)
					Value = Value.GetValue();

				var sQSVar = (sArg ? sArg + '[' + Key + ']' : Key);

				if (L.isArray(Value) || L.isObject(Value))
				{
					aParamParts.push(fnEncodeURLParams(Value, sQSVar));
				}
				else
				{
					aParamParts.push(encodeURIComponent(sQSVar) + '=' + encodeURIComponent(Value));
				}
			});

			return aParamParts.join('&');
		}

		sURL = sURL + '?' + fnEncodeURLParams(oParams);
		return sURL;
	},

	/**
	 * Parses values from a query-string.
	 * @param {String} sParamString the query-string to parse
	 * @return {Object} the parsed values
	 */
	ParseQueryString: function(sParamString)
	{
		var aTmpParams = sParamString.split(/&/),
			oParams = {};

		U.ForEach(aTmpParams, function(sParamPair)
		{
			var aPair = U.Map(sParamPair.split(/=/, 2), decodeURIComponent);
			if (!aPair[0].length || !L.isUndefined(oParams[aPair[0]]))
				return; // continue to next element

			oParams[aPair[0]] = aPair[1] || '';
		});

		return oParams;
	},

	// backwards compatibility:
	Alert: U.Alert,

	/**
	 * Creates a dialog box for a widget, and registers it with the overlay manager.
	 *
	 * @param {String} sCaption the window caption
	 * @param {String/HTMLElement} elBody the body of the dialog box
	 * @param {HTMLElement} [elContainer = document.body] the container for the dialog
	 * @param {Object} [oConfig] additional config for the dialog box
	 * @returns {Object} The YUI dialog object
	 */
	CreateDialog: function(sCaption, elBody, elContainer, oConfig)
	{
		var elDialogContainer = document.createElement('div'),
		    elDialogHeader = document.createElement('div'),
		    elDialogBody = document.createElement('div'),
			oDialog, fnConstructor;

		if (!elContainer.appendChild)
		{
			throw new Error("Invalid elContainer: " + L.dump(elContainer));
		}

		elDialogHeader.className = 'hd';
		elDialogHeader.innerHTML = sCaption;
		elDialogBody.className = 'bd clearfix';

		if (L.isString(elBody))
		{
			elDialogBody.innerHTML = elBody;
		}
		else
		{
			elDialogBody.appendChild(elBody);
		}
		elDialogContainer.appendChild(elDialogHeader);
		elDialogContainer.appendChild(elDialogBody);
		(elContainer || document.body).appendChild(elDialogContainer);

		oConfig = L.merge({
			close: true,
			draggable: true,
			visible: false
		}, (oConfig || {}));

		if (L.isUndefined(oConfig.effect) && YAHOO.env.getVersion('animation'))
		{
			oConfig.effect = { effect:YAHOO.widget.ContainerEffect.FADE, duration:0.25 };
		}

		// We don't want the submit-handling "features" of the Dialog widget, we handle all of that ourselves.
		// We do however use the "yui-dialog" CSS class to give us the right styling on the "default" button, in some places.
		fnConstructor = YAHOO.widget.Panel;
		if (!L.isUndefined(oConfig.buttons))
		{
			// we have buttons.. suggests the caller wants a dialog rather than just a panel.
			fnConstructor = YAHOO.widget.Dialog;
		}

		oDialog = new fnConstructor(elDialogContainer, oConfig);
		Dom.addClass(oDialog.element, YAHOO.widget.Dialog.CSS_DIALOG);

		// YUI is not adding this any more (not sure why) - it's required to stop the IE table bleed-through bug.
		Dom.addClass(oDialog.element, YAHOO.widget.Overlay.CSS_HIDDEN);

		this.oOverlayManager.register(oDialog);
		oDialog.render();
		oDialog.hide();
		return oDialog;
	},

	/**
	 * Destroys a dialog previously created with CreateDialog
	 * @param {Object} the dialog object to destruct
	 */
	DestroyDialog: function(oDialog)
	{
		if (oDialog && oDialog.element)
		{
			this.oOverlayManager.remove(oDialog);
			oDialog.destroy();
		}
	}
}

L.augmentObject(ZC.JSManager, GetWidgetProvider.prototype);

/**
 * Base class for the Block classes.
 *
 * @class Block
 * @constructor
 * @namespace ZC.Core
 * @uses ZC.AttribProvider
 * @uses ZC.GetWidgetProvider
 * @param {String} sName block name
 * @param {Array} aDef block definition
 * @param {Object} oParent parent block
 */
ZC.Core.Block = function(sName, aDef, oParent)
{
	/**
	 * Block name
	 * @type String
	 * @property sName
	 */
	this.sName = sName;

	/**
	 * Block definition. Use the methods from AttribProvider to access this.
	 * @type Object
	 * @property aDef
	 * @private
	 */
	this.aDef = aDef;

	/**
	 * Parent block
	 * @type Object
	 * @property oParent
	 */
	this.oParent = oParent;

	/**
	 * List of child blocks. Use GetWidget or GetBlocks* methods to access.
	 * @type Object
	 * @property aChildBlocks
	 * @private
	 */
	this.aChildBlocks = {};

	/**
	 * Collection of widgets on this block. Use GetWidget to access it.
	 * @type Object
	 * @property aWidgets
	 * @private
	 */
	this.aWidgets = {};

	/**
	 * Collection of forms on this block. Use GetWidget to access it.
	 * @type Object
	 * @property aForms
	 * @private
	 */
	this.aForms = {};

	//YAHOO.log("Calling CustomSetupStart", "debug", this.sName);
	if (!this.CustomSetupStart())
	{
		throw new Error('CustomSetupStart returned false');
	}
	//YAHOO.log("CustomSetupStart done", "debug", this.sName);

	this.ProcessConfig(aDef, true);

	//YAHOO.log("Calling CustomSetupEnd", "debug", this.sName);
	if (!this.CustomSetupEnd())
	{
		try { this.Destruct(); } // undo anything done by ProcessConfig (attaching to events, etc) 
		catch (e) { YAHOO.log("Error during destruction: " + e.message + " (" + e.fileName + ":" + e.lineNumber + ")", "warn", this.sName); }

		throw new Error('CustomSetupEnd returned false');
	}
	//YAHOO.log("CustomSetupEnd done", "debug", this.sName);

	this.AttribProviderSetup();
}

/**
 * Instantiates a new Block object given a definition. Looks for Module & Type
 * attribs in the def to locate the correct block class. If that class can't be
 * found, then the returned object will be an instance of ZC.Core.Block
 *
 * @static
 * @param {String} sName the block name
 * @param {Object} aDef the block definition
 * @param {Object} oParent the parent object
 * @return {Object} the new block object
 */
ZC.Core.Block.NewFromDef = function(sName, aDef, oParent)
{
	//YAHOO.log("Creating block called " + sName + " (" + (aDef.Module || 'Core') + "/" + aDef.Type + ")", "debug", "Core.Block.NewFromDef");
	var fnConstructor;
	if (!L.isUndefined(aDef.Type))
	{
	   	fnConstructor = ZC.JSManager.GetComponent(aDef.Type, aDef.Module || 'Core', 'Block');
	}

	if (L.isUndefined(fnConstructor))
	{
		// fall back to base class
		fnConstructor = ZC.Core.Block;
	}
	return new fnConstructor(sName, aDef, oParent);
}

/**
 * This static method creates an block class and extends the base
 * class. Creates the required namespace if it doesn't already exist.
 *
 * @static
 * @param {String} sBlockClassName the block name
 * @param {String} sModule the module for this block (defaults to Core)
 * @param {String} oParentClass the block to extend (defaults to the base block class)
 * @param {String} sType the block type, if not Block (e.g. Layout or Page)
 * @return {Object} the block object
 */
ZC.Core.Block.Create = function(sBlockClassName, sModule, oParentClass, sType)
{
	sModule = sModule || 'Core';
	oParentClass = oParentClass || ZC.Core.Block;
	sType = sType || 'Block';
	var oNS = ZC.Namespace(sModule + '.' + sType);

	oNS[sBlockClassName] = function(sName, aDef, oForm, oParent)
	{
		oNS[sBlockClassName].superclass.constructor.call(this, sName, aDef, oForm, oParent);
	}
	L.extend(oNS[sBlockClassName], oParentClass);

	return oNS[sBlockClassName];
}

ZC.Core.Block.prototype = {
	/**
	 * Called by the block constructor before the base block class has initialised.
	 *
	 * @return {Boolean} This method should return false if unable to complete the initialization, then the object will be discarded.
	 */
	CustomSetupStart: function()
	{
		return true;
	},

	/**
	 * Processes some configuration data containing new child blocks, widgets and forms.
	 * @param {Object} aDef the configuration data
	 * @param {Boolean} bConstructor (internal use) set to true when called from the constructor
	 */
	ProcessConfig: function(aDef, bConstructor)
	{
		if (!L.isUndefined(aDef.Blocks))
		{
			YAHOO.log("Started initialising child blocks", "debug", this.sName);
			U.ForEach(aDef.Blocks, function(aBlockDef, sBlockName)
			{
				if (L.isUndefined(this.aChildBlocks[sBlockName]) || !Dom.inDocument(this.aChildBlocks[sBlockName].GetElement()))
				{
					if (!L.isUndefined(this.aChildBlocks[sBlockName]))
					{
						try { this.aChildBlocks[sBlockName].Destruct() } 
						catch (e) { YAHOO.log("Error during destruction: " + e.message + " (" + e.fileName + ":" + e.lineNumber + ")", "warn", this.sName); }

						this.aChildBlocks[sBlockName] = null;
					}
					try 
					{
						this.AddChildBlock(sBlockName, aBlockDef);
					}
					catch (ex)
					{
						YAHOO.log('Unable to add block ' + sBlockName + ' to ' + this.sName + ': ' + ex.message + " (" + ex.fileName + ":" + ex.lineNumber + ")", 'error', this.sName);
					}
				}
				else
				{
					this.aChildBlocks[sBlockName].ProcessConfig(aBlockDef);
				}
			}, this);
			YAHOO.log("Finished initialising child blocks", "debug", this.sName);

			delete aDef.Blocks;
		}

		if (!L.isUndefined(aDef.Widgets))
		{
			YAHOO.log("Started initialising widgets", "debug", this.sName);
			U.ForEach(aDef.Widgets, function(aWidgetDef, sWidgetName)
			{
				if (L.isUndefined(this.aWidgets[sWidgetName]) || !Dom.inDocument(this.aWidgets[sWidgetName]._elInput))
				{
					if (!L.isUndefined(this.aWidgets[sWidgetName]))
					{
						try { this.aWidgets[sWidgetName].Destruct() } 
						catch (e) { YAHOO.log("Error during destruction: " + e.message + " (" + e.fileName + ":" + e.lineNumber + ")", "warn", this.sName); }

						this.aWidgets[sWidgetName] = null;
					}
					try 
					{
						this.aWidgets[sWidgetName] = ZC.Core.Widget.NewFromDef(sWidgetName, aWidgetDef, undefined, this);
					}
					catch (ex)
					{
						YAHOO.log('Unable to add widget ' + sWidgetName + ' to ' + this.sName + ': ' + ex.message + " (" + ex.fileName + ":" + ex.lineNumber + ")", 'error', this.sName);
					}	
				}
				else
				{
					this.aWidgets[sWidgetName].ProcessConfig(aWidgetDef);
				}
			}, this);
			YAHOO.log("Finished initialising widgets", "debug", this.sName);

			delete aDef.Widgets;
		}

		if (!L.isUndefined(aDef.Forms))
		{
			YAHOO.log("Started initialising forms", "debug", this.sName);
			U.ForEach(aDef.Forms, function(aFormDef, sFormName)
			{
				if (L.isUndefined(this.aForms[sFormName]) || !Dom.inDocument(this.aForms[sFormName]._elInput))
				{
					if (!L.isUndefined(this.aForms[sFormName]))
					{
						try { this.aForms[sFormName].Destruct() } 
						catch (e) { YAHOO.log("Error during destruction: " + e.message + " (" + e.fileName + ":" + e.lineNumber + ")", "warn", this.sName); }

						this.aForms[sFormName] = null;
					}
					try
					{
						this.aForms[sFormName] = new ZC.Core.Form(sFormName, aFormDef, this);
						ZC.JSManager.RegisterForm(this.aForms[sFormName]);
					}
					catch (ex)
					{
						YAHOO.log('Unable to add form ' + sFormName + ' to ' + this.sName + ': ' + ex.message + " (" + ex.fileName + ":" + ex.lineNumber + ")", 'error', this.sName);
					}
				}
				else
				{
					this.aForms[sFormName].ProcessConfig(aFormDef);
				}
			}, this);
			YAHOO.log("Finished initialising forms", "debug", this.sName);

			delete aDef.Forms;
		}

		if (!L.isUndefined(aDef.EventListeners))
		{
			this.AddEventListeners(aDef.EventListeners);
			delete aDef.EventListeners;
		}

		if (!L.isUndefined(aDef.Events))
		{
			this.AddEvents(aDef.Events);
			delete aDef.Events;
		}

		if (!bConstructor)
		{
			this.SetAttribs(aDef);
		}
	},

	/**
	 * Adds a child block to this object.
	 * @param {String} sBlockName the name for the new block
	 * @param {Object} aBlockDef the definition for the new block
	 * @return {Object} the new block object
	 */
	AddChildBlock: function(sBlockName, aBlockDef)
	{
		return (this.aChildBlocks[sBlockName] = ZC.Core.Block.NewFromDef(sBlockName, aBlockDef, this));
	},

	/**
	 * Called by the block constructor after the base block class has initialised.
	 *
	 * @return {Boolean} This method should return false if unable to complete the initialization, then the object will be discarded.
	 */
	CustomSetupEnd: function()
	{
		return true;
	},

	/**
	 * Frees up all the references in this object. Called on page unload to avoid memory leaks in IE.
	 */
	Destruct: function()
	{
		this.EventProviderDestruct();

		U.ForEach(this.aChildBlocks, function(oBlock) { 
			try { oBlock.Destruct(); } 
			catch (e) { YAHOO.log("Error during destruction: " + e.message + " (" + e.fileName + ":" + e.lineNumber + ")", "warn", this.sName); }
		});
		U.ForEach(this.aWidgets, function(oWidget) { 
			try { oWidget.Destruct(); } 
			catch (e) { YAHOO.log("Error during destruction: " + e.message + " (" + e.fileName + ":" + e.lineNumber + ")", "warn", this.sName); }
		});
		U.ForEach(this.aForms, function(oForm) { 
			try { oForm.Destruct(); } 
			catch (e) { YAHOO.log("Error during destruction: " + e.message + " (" + e.fileName + ":" + e.lineNumber + ")", "warn", this.sName); }
		});

		if (!L.isUndefined(this._ajaxDialog))
		{
			ZC.JSManager.DestroyDialog(this._ajaxDialog);
			this._ajaxDialog = undefined;
		}

		this.oParent = null;
		this.aChildBlocks = {};
		this.aWidgets = {};
		this.aForms = {};
		this.elBlock = null;
	},

	/**
	 * If the block element ids have a common prefix, set this classvar so that GetElement will find them.
	 * @protected
	 * @type String
	 */
	_IDPrefix: 'blk',

	/**
	 * Gets the element which relates to this block.
	 * @return {HTMLElement} the block element
	 */
	GetElement: function()
	{
		if (L.isUndefined(this.elBlock))
		{
			this.elBlock = Dom.get(this._IDPrefix + this.UniqueID());
		}

		return this.elBlock;
	},

	/**
	 * Returns the element(s) to add events to.
	 * @return {Object/Array} either an element, or array of elements.
	 */
	GetEventElement: function()
	{
		return this.GetElement();
	},

	/**
	 * Returns the block's unique ID. Mirrors the server-side function.
	 *
	 * @return {String} unique identifier for this block
	 */
	UniqueID: function()
	{
		if (this.AttribIsset('BlockID'))
		{
			return this.GetAttrib('BlockID').toString();
		}
		else if (this.oParent)
		{
			return this.oParent.UniqueID() + '_' + this.sName;
		}
		else
		{
			return this.sName;
		}
	},

	/**
	 * Retrieves a block by UniqueID.
	 * @param {String} sUniqueID to locate
	 * @return {Object} A block object, or <code>undefined</code> if not found
	 */
	GetBlockByUniqueID: function(sUniqueID)
	{
		for (var sName in this.aChildBlocks)
		{
			if (L.hasOwnProperty(this.aChildBlocks, sName))
			{
				if (this.aChildBlocks[sName].UniqueID() == sUniqueID)
					return this.aChildBlocks[sName];

				var oChildBlock = this.aChildBlocks[sName].GetBlockByUniqueID(sUniqueID);
				if (oChildBlock)
					return oChildBlock;
			}
		}
		return undefined;
	},

	/**
	 * Sends an AJAX request to the server, to be processed by the server-side equivalent of this block object.
	 *
	 * Takes an object of parameters, can contain the following keys: 
	 * <dl>
	 * <dt> Transaction </dt><dd> name of the AJAX transaction, if specified this will call ProcessAjax_&lt;Transaction&gt; on the server</dd>
	 * <dt> URL         </dt><dd> override the URL that is used for making the request (defaults to current page).</dd>
	 * <dt> GetPostVars </dt><dd> containing name => value/object pairs (as accepted by ZC.JSManager.URL), extra parameters to put on the query</dd>
	 * <dt> Method      </dt><dd> request method ("GET" or "POST", default "GET")</dd>
	 * <dt> CustomObj   </dt><dd> a custom object that is passed to the ProcessAjaxResponse method</dd>
	 * <dt> Form        </dt><dd> a form element, if present will be passed to the YUI Connection Manager for serialisation.</dd>
	 * <dt> Scope       </dt><dd> scope for the On* functions, defaults to this block object. </dd>
	 * <dt> OnSuccess   </dt><dd> a function to run after the request has been successfully processed.</dd>
	 * <dt> OnFailure   </dt><dd> a function to run after if there is a error with the request.</dd>
	 * <dt> OnComplete  </dt><dd> a function to run after the request has returned, regardless of the status.</dd>
	 * <dt> LoadingIndicator</dt><dd> an object that controls a "loading" indicator. It should define two methods, Show and Hide. If not specified a default is used. </dd>
	 * </dl> 
	 *
	 * <em>NOTE:</em> For convenience, if <code>oParams</code> is just a string, then this specifies the "Transaction" attrib, all other options being defaults.
	 *
	 * All parameters are optional.
	 * @param {Object} oParams
	 */
	AjaxRequest: function(oParams)
	{
		var fnRequestSuccess,
			sServerError = '',
			fnFormatErrors = function(oErr) { 
				if (oErr.Backtrace) sServerError += '<span title="' + oErr.Backtrace.replace(/\n+/g, " => ") + '">';

				sServerError += oErr.Message;

				if (oErr.Backtrace) sServerError += '</span>';

				sServerError += '<br><br>'; 
			};

		if (L.isUndefined(oParams))
			oParams = {};
		if (L.isString(oParams))
			oParams = { Transaction: oParams };

		if (L.isUndefined(oParams.OnComplete))
			oParams.OnComplete = function() {};
		if (L.isUndefined(oParams.OnSuccess))
			oParams.OnSuccess = function() {};
		if (L.isUndefined(oParams.Scope))
			oParams.Scope = this;
		if (L.isUndefined(oParams.LoadingIndicator))
			oParams.LoadingIndicator = U.DefaultLoadingIndicator;

		if (L.isUndefined(oParams.OnFailure))
		{
			if (ZC.JSManager.oConfig.DEBUG)
			{
				oParams.OnFailure = function(iCode, sStatus, oResp) { 
					if (iCode != -2 && oResp && oResp.responseText) // iCode is -2 if JSON parsing failed, don't bother trying again...
					{
						try 
						{
							oJSONResponse = YAHOO.lang.JSON.parse(oResp.responseText);
							if (oJSONResponse.Errors)
							{
								U.ForEach(oJSONResponse.Errors, fnFormatErrors);
							}
						}
						catch (e) {}
					}
					else if(iCode == -2)
					{
						// probably a PHP fatal error
						sServerError = oResp.responseText;
					}

					U.Alert("AJAX processing error: " + iCode + " " + sStatus + '<br>' + sServerError + '<br>' + (oResp ? oResp.responseXML : '')); 
				};
			}
			else
			{
				oParams.OnFailure = function() {};
			}
		}

		fnRequestSuccess = function(oResp)
		{
			var Get = YAHOO.util.Get,
				oParams = oResp.argument,
				sResponseText,
				oJSONResponse,
				oOnLoadEvent = ZC.JSManager.GetEvent('OnLoadEvent'),
				fnLoadJS,
				fnLoadCSS,
				fnProcessBlocks;

			oOnLoadEvent.unsubscribeAll();

			fnLoadJS = function()
			{
				YAHOO.log("Loading JS..", "debug", "AjaxRequest");
				var aJS = U.Filter(oJSONResponse.LoadJS, function(sJS) { 
					if (!sJS.match(/^https?:\/\//))
						sJS = window.location.protocol + '//' + window.location.hostname + sJS;

					// Dom.getElementBy returns an empty array if no match was found
					return L.isArray(Dom.getElementBy(function(el) { return (el.src == sJS); }, 'script', document.getElementsByTagName('head')[0]));
				});
				Get.script(aJS, { 
					onSuccess: fnLoadCSS,
					onFailure: function(o)
					{
						oParams.LoadingIndicator.Hide();
						oParams.OnComplete.call(oParams.Scope, oResp);
						oParams.OnFailure.call(oParams.Scope, -4, 'JS loading error: ' + o.msg, oResp);
					},
					scope: this 
				});
			}
			fnLoadCSS = function()
			{
				YAHOO.log("Loading CSS..", "debug", "AjaxRequest");
				U.ForEach(oJSONResponse.LoadCSSMedia, function(sMedia, iIndex)
				{
					var aCSS,
						elHead,
						oGetDef = { 
							attributes: { media: sMedia },
							onFailure: function(o)
							{
								oParams.LoadingIndicator.Hide();
								oParams.OnComplete.call(oParams.Scope, oResp);
								oParams.OnFailure.call(oParams.Scope, -5, 'CSS loading error: ' + o.msg, oResp);
							},
							scope: this
						};

					if (iIndex == (oJSONResponse.LoadCSSMedia.length - 1))
					{
						oGetDef.onSuccess = fnProcessBlocks;
					}
					elHead = document.getElementsByTagName('head')[0];
					// filter out any CSS files we have already loaded
					// this isn't just for efficiency.. loading Core.css twice seems to trigger some FF bugs.
					aCSS = U.Filter(oJSONResponse.LoadCSS[sMedia], function(sCSS) { 
						var iLen = sCSS.length;

						// Dom.getElementBy returns an empty array if no match was found
						return L.isArray(Dom.getElementBy(function(el) { 
							// some browsers include the domain in the href
							// value, even if it's not present in the document.
							// only check the rightmost part of the string for equality.
							var iLen2 = el.href.length;
							if (iLen2 < iLen)
								return false;

							return (el.href.substr(iLen2 - iLen) == sCSS); 
						}, 'link', elHead));
					});
					elHead = null;
					Get.css(aCSS, oGetDef);
				}, this);
			}
			fnProcessBlocks = function()
			{
				YAHOO.log("Processing block updates", "debug", "AjaxRequest");
				var oFinishEvent = new YAHOO.util.CustomEvent(), oFinishTimer;
				oFinishEvent._updateAnimDone = true; // if animations need to be done to update elements, we need to defer the finish event until the animations are done.

				U.ForEach(oJSONResponse.Blocks, function(aBlockData, sBlockUID)
				{
					YAHOO.log("Processing updates for " + sBlockUID, "debug", "AjaxRequest");
					var oBlock;

					if (L.isFunction(this.UniqueID) && sBlockUID == this.UniqueID())
					{
						oBlock = this;
					}
					else
					{
						oBlock = ZC.JSManager.GetBlockByUniqueID(sBlockUID);
					}

					if (oBlock)
					{
						oBlock.ProcessAjaxResponse(aBlockData, oParams.CustomObj, oFinishEvent);
					}
					else
					{
						YAHOO.log("Unable to find block with ID " + sBlockUID, "error", "ProcessAjaxResponse");
					}
				}, this);

				oFinishTimer = L.later(100, this, function() {
					if (oFinishEvent._updateAnimDone)
					{
						oFinishEvent.fire();
						oOnLoadEvent.fire();
						oParams.LoadingIndicator.Hide();
						oParams.OnSuccess.call(oParams.Scope);
						oFinishTimer.cancel();
					}
				}, {}, true);
			}

			try 
			{
				YAHOO.log("Parsing JSON response", "debug", "AjaxRequest");
				oJSONResponse = YAHOO.lang.JSON.parse(oResp.responseText);
				YAHOO.log("Parsed OK", "debug", "AjaxRequest");
			}
			catch (e)
			{
				oParams.LoadingIndicator.Hide();
				oParams.OnComplete.call(oParams.Scope, oResp);
				oParams.OnFailure.call(oParams.Scope, -2, "JSON parse error", oResp);
				return;
			}

			if (oJSONResponse.GoToPage)
			{
				YAHOO.log("Redirecting to " + oJSONResponse.GoToPage, "debug", "AjaxRequest");
				window.location = oJSONResponse.GoToPage;
				return;
			}

			if (ZC.JSManager.oConfig.DEBUG && oJSONResponse.Errors)
			{
				U.ForEach(oJSONResponse.Errors, fnFormatErrors);
				U.Alert(sServerError);
			}

			ZC.JSManager.LoadYUILib({
				require: U.Filter(oJSONResponse.LoadYUI, function(sReq) { return (YAHOO.env.getVersion(sReq) == null); }), 
				onSuccess: fnLoadJS, 
				onFailure: function(o) { 
					oParams.LoadingIndicator.Hide();
					oParams.OnComplete.call(oParams.Scope, oResp);
					oParams.OnFailure.call(oParams.Scope, -3, 'YUI loading error: ' + o.msg, oResp);
				},
				scope: this
			});
		}

		oParams.LoadingIndicator.Show();

		// load connection manager & json library if necessary
		ZC.JSManager.LoadYUILib({
			require: ["connection", "json"],
			scope: this,
			onFailure: function(o) { U.Alert(o.msg); },
			onSuccess: function()
			{
				var Connect = YAHOO.util.Connect,
					bUseFileUpload, bSecure,
					aListeners,
					oGetPostVars = oParams.GetPostVars || {},
					oCallback = {
						scope: this,
						argument: oParams,
						failure: function(o) { 
							oParams.LoadingIndicator.Hide();
							oParams.OnComplete.call(oParams.Scope, o); 
							oParams.OnFailure.call(oParams.Scope, o.status, o.statusText, o); 
						},
						// work around a little discrepancy in the Connection Manager API - when an "upload" request is done, the responseText is HTML-escaped.
						// we can get the text value via responseXML though, which contains the document element from the iframe.
						// upstream bug is: http://yuilibrary.com/projects/yui2/ticket/2528941
						upload:  function(o) { var b = o.responseXML.body; o.responseText = (b.textContent || b.innerText); fnRequestSuccess.call(this, o); },
						success: fnRequestSuccess
					};

				// Use conditional comments to add an IE version header to the request. 
				// We use this instead of User-Agent, as it's more reliable (won't be confused by UA switchers)
				/*@cc_on
					Connect.initHeader("X-IE-Version", YAHOO.env.ua.ie);
				@*/

				if (L.isString(oGetPostVars))
				{
					oGetPostVars = ZC.JSManager.ParseQueryString(oGetPostVars);
				}
				
				if (!L.isUndefined(oParams.Form))
				{
					bUseFileUpload = (oParams.Form.enctype == "multipart/form-data");
					bSecure = (bUseFileUpload && YAHOO.env.ua.ie && window.location.protocol.toLowerCase() == "https:");
					Connect.setForm(oParams.Form, bUseFileUpload, bSecure);

					oParams.Method = oParams.Method || oParams.Form.method;
				}

				if (oParams.Transaction)
				{
					oGetPostVars._ajaxTr = oParams.Transaction;
				}

				if (oParams.Poll)
				{
					oGetPostVars._ajaxPoll = 1;
				}
				else
				{
					oGetPostVars._ajaxBlk = this.UniqueID();
				}
				Connect.asyncRequest(oParams.Method || 'POST', ZC.JSManager.URL(oGetPostVars, (oParams.URL || window.location.href.replace(/#.*$/, ''))), oCallback);
			}
		});
	},

	/**
	 * Performs an Ajax "poll" request. This simply makes the server check all blocks on the page to see if they want to update.
	 */
	AjaxPoll: function()
	{
		this.AjaxRequest({ Poll: true });
	},

	/**
	 * Processes the response from an AjaxRequest.
	 * @param {Object} oResponse the data sent back from the server
	 * @param {Object} oCustom the custom object passed into AjaxRequest
	 * @param {Object} oFinishEvent a custom event that is fired once all of the AJAX response has been processed.
	 */
	ProcessAjaxResponse: function(oResponse, oCustom, oFinishEvent)
	{
		U.ForEach(oResponse, function(oData, sAction)
		{
			switch (sAction)
			{
				case 'UpdateElement':
					U.ForEach(oData, function(oContent, sID)
					{
						oFinishEvent._updateAnimDone = false;
						oContent.OnComplete = function() { oFinishEvent._updateAnimDone = true; }
						U.UpdateElement(sID, oContent);
					});
					break;

				case 'Alert':
					U.Alert(oData.Message, oData);
					break;

				case 'Dialog':
					if (!L.isUndefined(this._ajaxDialog))
					{
						ZC.JSManager.DestroyDialog(this._ajaxDialog);
					}
					this._ajaxDialog = ZC.JSManager.CreateDialog(oData.Header, oData.Body, document.body, oData);
					this._ajaxDialog.setFooter(oData.Footer);
					this._ajaxDialog.show();

					var aForms = this._ajaxDialog.element.getElementsByTagName('form');
					if (aForms)
					{
						U.ForEach(aForms, function(elForm) { elForm._ajaxDialog = this._ajaxDialog; }, this);
					}
					break;

				case 'ProcessConfig':
					oFinishEvent.subscribe(function() { this.ProcessConfig(oData); }, this, true);
					break;

				case 'ScheduleRequest':
					if (L.isUndefined(this._aScheduledRequests))
						this._aScheduledRequests = [];

					U.ForEach(oData, function (mRequest)
					{
						if (!U.Some(this._aScheduledRequests, function(o) { return U.ObjectsEqual(o, mRequest); }))
							this._aScheduledRequests.push(mRequest);
					}, this);

					if (L.isUndefined(this._oScheduledRequestTimer))
					{
						this._oScheduledRequestTimer = oFinishEvent.subscribe(this._RunScheduledRequests, this, true);
					}
					break;

				case 'FireEvent':
					U.ForEach(oData, function (aParamObjects, sEventName)
					{
						var oEvent = ZC.JSManager.GetEvent(sEventName);
						U.ForEach(aParamObjects, oEvent.fire, oEvent);
					});
					break;
			}
		}, this);
	},

	/**
	 * Runs the requests in _aScheduledRequests
	 * @private
	 */
	_RunScheduledRequests: function()
	{
		var mRequest;

		// using shift() should cope with requests that are scheduled while processing other scheduled requests
		while (mRequest = this._aScheduledRequests.shift())
		{
			this.AjaxRequest(mRequest);
		}

		this._oScheduledRequestTimer = undefined;
	},

	/** 
	 * @private 
	 */
	_aSearchObjects: [ 'aWidgets', 'aChildBlocks', 'aForms' ]
}

L.augment(ZC.Core.Block, AttribProvider);
L.augment(ZC.Core.Block, GetWidgetProvider);
L.augment(ZC.Core.Block, EventProvider);

/**
 * Base class for Layouts.
 *
 * @class Layout
 * @constructor
 * @namespace ZC.Core
 * @extends ZC.Core.Block
 * @param {String} sName block name
 * @param {Array} aDef block definition
 * @param {Object} oParent parent block
 */
ZC.Core.Layout = function(sName, aDef, oParent)
{
	ZC.Core.Layout.superclass.constructor.call(this, sName, aDef, oParent);
}
L.extend(ZC.Core.Layout, ZC.Core.Block);

/**
 * Base class for Pages.
 *
 * @class Page
 * @constructor
 * @namespace ZC.Core
 * @extends ZC.Core.Block
 * @param {String} sName block name
 * @param {Array} aDef block definition
 * @param {Object} oParent parent block
 */
ZC.Core.Page = function(sName, aDef, oParent)
{
	ZC.Core.Page.superclass.constructor.call(this, sName, aDef, oParent);
}
L.extend(ZC.Core.Page, ZC.Core.Block);

/**
 * Base class for all the Widget classes.
 *
 * @class Widget
 * @namespace ZC.Core
 * @uses ZC.AttribProvider
 * @uses ZC.GetWidgetProvider
 * @constructor
 * @param {String} sName widget name
 * @param {Array} aDef an associative array containing the widget definition
 * @param {Object} oParent the parent of this widget, if available
 */
ZC.Core.Widget = function(sName, aDef, oForm, oParent)
{
	//YAHOO.log("Started initialising " + sName, "debug", sName);

	/**
	 * Widget name
	 * @property sName
	 * @type String
	 */
	this.sName = sName;

	/**
	 * Widget definition
	 * @property aDef
	 * @type Object
	 */
	this.aDef = aDef;

	/**
	 * Parent widget
	 * @property oParent
	 * @type Object
	 */
	this.oParent = oParent;

	/**
	 * Form containing this widget
	 * @property oForm
	 * @type ZC.Core.Form
	 */
	this.oForm = oForm;

	/**
	 * Array of child widgets. Use GetWidget to access.
	 * @property aChildWidgets
	 * @type Object
	 * @private
	 */
	this.aChildWidgets = {};

	this._FindElements();
	if (!this.CustomSetupStart())
	{
		throw new Error("CustomSetupStart returned false");
	}

	this.ProcessConfig(this.aDef, true);

	if (!L.isUndefined(aDef.ValidateOnEvents))
	{
		this.AddEvent(this.Validate, aDef.ValidateOnEvents, this);
	}

	if (!this.CustomSetupEnd())
	{
		throw new Error("CustomSetupEnd returned false");
	}
	//YAHOO.log("Finished initialising " + sName, "debug", sName);
	
	this.AttribProviderSetup();
}

/**
 * This static method creates an widget class and extends the base
 * class. Creates the required namespace if it doesn't already exist.
 *
 * @param {String} sWidgetClassName the widget name
 * @param {String} sModule the module for this widget (defaults to Core)
 * @param {String} oParentClass the widget to extend (defaults to the base widget class)
 * @return {Object} the widget object
 */
ZC.Core.Widget.Create = function(sWidgetClassName, sModule, oParentClass)
{
	sModule = sModule || 'Core';
	oParentClass = oParentClass || ZC.Core.Widget;
	var oNS = ZC.Namespace(sModule + '.Widget');

	oNS[sWidgetClassName] = function()
	{
		oNS[sWidgetClassName].superclass.constructor.apply(this, arguments);
	}
	L.extend(oNS[sWidgetClassName], oParentClass);

	return oNS[sWidgetClassName];
}

/**
 * Static factory for instantiating widgets from a widget def.
 *
 * This method checks the def for a module / type name (WidgetType overrides
 * Type), and tries to load the object relating to that widget. If it can't
 * find the object, it falls back to using the base class.
 *
 * @param {String} sName widget name
 * @param {Array} aDef widget definition
 * @param {Object} oParent (optional) parent widget
 * @return {Object} the new widget
 */
ZC.Core.Widget.NewFromDef = function(sName, aDef, oForm, oParent)
{
	var fnConstructor = ZC.JSManager.GetComponent(aDef.WidgetType || aDef.Type, aDef.Module, 'Widget');
	if (L.isUndefined(fnConstructor))
	{
		// fall back to base class
		fnConstructor = ZC.Core.Widget;
	}
	return new fnConstructor(sName, aDef, oForm, oParent);
}

ZC.Core.Widget.prototype = {
	/**
	 * Called by the widget constructor before the base widget class has initialised.
	 *
	 * @return {Boolean} This method should return false if unable to complete the initialization, then the Javascript object will be discarded.
	 */
	CustomSetupStart: function()
	{
		return true;
	},

	/**
	 * Processes configuration data
	 * @param {Object} aDef the configuration to process
	 * @param {Boolean} bConstructor (internal use) set to true when called from the constructor
	 */
	ProcessConfig: function(aDef, bConstructor)
	{
		if (!L.isUndefined(aDef.Widgets))
		{
			//YAHOO.log("Started initialising Widgets", "debug", sName);
			U.ForEach(aDef.Widgets, function(aWidgetDef, sWidgetName)
			{
				if (L.isUndefined(this.aChildWidgets[sWidgetName]) || !Dom.inDocument(this.aChildWidgets[sWidgetName]._elInput))
				{
					if (!L.isUndefined(this.aChildWidgets[sWidgetName]))
					{
						try { this.aChildWidgets[sWidgetName].Destruct() } 
						catch (e) { YAHOO.log("Error during destruction: " + e.message + " (" + e.fileName + ":" + e.lineNumber + ")", "warn", this.sName); }

						this.aChildWidgets[sWidgetName] = null;
					}
					try 
					{
						this.AddChildWidget(sWidgetName, aWidgetDef);
					}
					catch (ex)
					{
						YAHOO.log('Unable to add widget ' + sWidgetName + ' to ' + this.sName + ': ' + ex.message + " (" + ex.fileName + ":" + ex.lineNumber + ")", 'error', this.sName);
					}
				}
				else
				{
					this.aChildWidgets[sWidgetName].ProcessConfig(aWidgetDef);
				}
			}, this);
			//YAHOO.log("Finished initialising Widgets", "debug", sName);
			
			delete aDef.Widgets;
		}
	
		// Check that the widget has been initialised OK. We need either an _elInput or some child widgets.
		if ((!this._elInput || (L.isArray(this._elInput) && this._elInput.length == 0)) && !this.HasChildWidgets())
		{
			throw new Error("No input element or child widgets");
		}

		if (!L.isUndefined(aDef.EventListeners))
		{
			this.AddEventListeners(aDef.EventListeners);
			delete aDef.EventListeners;
		}
		// make sure Validation is an array of Validator => msg or null
		if (L.isUndefined(aDef.Validation))
		{
			aDef.Validation = {};
		}
		else if (L.isString(aDef.Validation))
		{
			var sValidator = aDef.Validation;
			aDef.Validation = {};
			aDef.Validation[sValidator] = null;
		}
		else
		{
			var oNumberRegex = /^\d+$/;
			U.ForEach(aDef.Validation, function(sValidator, Key, aValidation)
			{
				if (oNumberRegex.test(Key))
				{
					aValidation[sValidator] = null;
					delete aValidation[Key];
				}
			});
		}
		this.aDef.Validation = L.merge(this.aDef.Validation || {}, aDef.Validation);

		if (!L.isUndefined(aDef.Events))
		{
			this.AddEvents(aDef.Events);
			delete aDef.Events;
		}

	},

	/**
	 * Called by the widget constructor after the base widget class has initialised.
	 *
	 * @return {Boolean} This method should return false if unable to complete the initialization, then the Javascript object will be discarded.
	 */
	CustomSetupEnd: function()
	{
		return true;
	},

	/**
	 * Frees up all the references in this object. Called on page unload to avoid memory leaks in IE.
	 *
	 * <p><em>NOTE:</em> destructors should cope with being called more than once, or being
	 * called on a half-initialised object. Check object references are good
	 * before trying to call methods on them!</p>
	 */
	Destruct: function()
	{
		U.ForEach(this.aChildWidgets, function(oWidget) { 
			try { oWidget.Destruct(); } 
			catch (e) { YAHOO.log("Error during destruction: " + e.message + " (" + e.fileName + ":" + e.lineNumber + ")", "warn", this.sName); }
		});
		this.EventProviderDestruct();

		this.oParent = null;
		this.oForm = null;
		this.aChildWidgets = {};
		this._elInput = null;
		this.elContainer = null;
		this.elValidationContainer = null;
		this.elRODisplay = null;
	},

	/**
	 * Works out the element ID from the widget/form/parent name
	 * @protected
	 * @return {String} element id
	 */
	_WidgetNameToID: function()
	{
		var sID = '';

		if (!L.isUndefined(this.aDef.ID))
			return this.aDef.ID;

		if (!L.isUndefined(this.oForm))
			sID += this.oForm.sName + '.';

		if (this.AttribIsset('HTMLNamePrefix'))
			sID += this.GetAttrib('HTMLNamePrefix');

		sID += this.sName;

		return sID;
	},

	/**
	 * Populates _elInput, then calls _FindContainers to populate elContainer
	 * and elValidationContainer.  This default implementation checks the def
	 * for an id, otherwise looks for elements with an ID made up of the widget
	 * name and any parent widget names.
	 * @protected
	 */
	_FindElements: function() {
		var sID = this._WidgetNameToID();

		this._elInput = Dom.get(sID);
		if (this._elInput)
			this._FindContainers();
	},

	/**
	 * By default, the _FindContainers method adds a change handler that updates the "ro-display" element for this widget.
	 * Set this to false to suppress this behaviour.
	 * @property bChangeRODisplay
	 * @type Boolean
	 * @protected
	 */
	bChangeRODisplay: true,

	/**
	 * Populates elContainer and elValidationContainer.
	 * @protected
	 */
	_FindContainers: function()
	{
		var elInput;
		if (L.isArray(this._elInput))
			if (this._elInput.length && this._elInput[0])
				elInput = this._elInput[0];
			else
				return;
		else
			if (this._elInput)
				elInput = this._elInput;
			else
				return;

		this.elContainer = Dom.getAncestorByClassName(elInput, 'form_field') || Dom.get('tr_' + elInput.id) || elInput.parentNode;
		if (this.elContainer)
		{
			if (L.isUndefined(this.elContainer._zcRefCount))
				this.elContainer._zcRefCount = 1;
			else
				++this.elContainer._zcRefCount;

			var aValContainers = Dom.getElementsByClassName('form_validation', null, this.elContainer);
			if (!aValContainers.length)
				aValContainers = Dom.getElementsByClassName('validation', null, this.elContainer);

			if (aValContainers.length)
			{
				this.elValidationContainer = aValContainers[0];
			}
			else if (!this.aDef.NoCreateValidation)
			{
				this.elValidationContainer = document.createElement('span');
				this.elValidationContainer.className = 'form_validation hide';
				elInput.parentNode.appendChild(this.elValidationContainer);
			}
		}

		this.elRODisplay = Dom.get(elInput.id + '_rodisplay');
		if (this.elRODisplay && this.bChangeRODisplay)
		{
			this.AddEvent(function() { this.elRODisplay.innerHTML = this.GetHTMLValue(); }, 'change', this);
		}
	},

	/**
	 * Gets the value of this widget.
	 * @return the widget value
	 */
	GetValue: function()
	{
		if (!this._elInput || L.isUndefined(this._elInput.value))
			return undefined;

		return this._elInput.value;
	},

	/**
	 * @return {Boolean} true if the widget has a value.
	 */
	HasValue: function()
	{
		var mValue = this.GetValue();
		if (L.isString(mValue))
		{
			mValue = mValue.replace(/(^\s*|\s*$)/g, '');
		}
		else if (mValue === 0)
		{
			return true;
		}

		return Boolean(mValue);
	},

	/**
	 * Sets the value of this widget
	 * @param Value the value to set the widget to
	 */
	SetValue: function(Value)
	{
		if (!this._elInput || L.isUndefined(this._elInput.value))
		{
			//YAHOO.log("this._elInput is undefined and SetValue is not overridden", "error", this.sName + "#SetValue");
			return undefined;
		}

		if (Value == this._elInput.value)
			return;

		this._elInput.value = Value;

		// reset validation status
		this._bIsValid = undefined;

		this._FireEventHandlers('change');
	},

	/**
	 * Returns the text value of the widget. Used for things like Select and Radio widgets where the caption needs looking up.
	 * @return {String} the text value of the widget
	 */
	GetTextValue: function()
	{
		return this.GetValue();
	},

	/**
	 * Returns the HTML value of the widget. Used for things like Select and Radio widgets where the caption needs looking up.
	 * @return {String} the HTML value of the widget
	 */
	GetHTMLValue: function()
	{
		return this.GetTextValue();
	},

	/**
	 * Gets the caption for this widget. If not set in the def, look for the label element relating to elInput.
	 * @return {String} the widget caption
	 */
	GetCaption: function()
	{
		if (this.aDef.Caption)
			return this.aDef.Caption;

		if (this._elInput)
		{
			var elLabel = this.GetLabelEl();
			return L.isUndefined(elLabel) ? undefined : (elLabel.innerText || elLabel.textContent);
		}

		return undefined;
	},

	/**
	 * Returns the label element for this widget
	 */
	GetLabelEl: function()
	{
		var sInputId;

		if (this._elInput && (sInputId = this._elInput.id)) // assignment
		{
			return Dom.getElementBy(function(el) { return Dom.getAttribute(el, 'for') == sInputId; }, 'label');
		}

		return undefined;
	},

	/**
	 * Checks if the widget is read-only
	 * @return {boolean} true if the widget is readonly
	 */
	IsReadOnly: function()
	{
		return (this.GetAttribDefault('DisplayAs', '').toLowerCase() == 'readonly');
	},

	/**
	 * Clears the widget. Mainly used for searching, this should set the widget to the value it would have on an empty search form.
	 */
	Clear: function()
	{
		if (!this._elInput && !this.HasChildWidgets())
		{
			//YAHOO.log('this._elInput is undefined, no child widgets and Clear is not overridden', "error", this.sName + "#Clear");
			return;
		}

		if (this._elInput && this._elInput.type)
		{
			var sInputType = L.isArray(this._elInput) ? 'array' : this._elInput.type.toLowerCase();
			if (sInputType != 'button' && sInputType != 'submit')
				this.SetValue('');
		}

		U.ForEach(this.aChildWidgets, function(oWidget) { oWidget.Clear() });
	},

	/**
	 * Enables / disables the widget
	 * @param {Boolean} bEnable if false, then this method disables the widget
	 * @param {String} sEnableClass if set, this class is added to an enabled widget, and removed from a disabled one
	 * @param {String} sDisableClass if set, this class is removed from an enabled widget, and added to a disabled one
	 */
	Enable: function(bEnable, sEnableClass, sDisableClass)
	{
		if (!this._elInput && !this.HasChildWidgets())
		{
			//YAHOO.log('this._elInput is undefined, no child widgets and Enable is not overridden', "error", this.sName + "#Enable");
			return undefined;
		}

		if (L.isUndefined(bEnable))
			bEnable = true;

		if (this._elInput)
		{
			Dom.batch(this._elInput, function(el)
			{
				if (L.isUndefined(el.parentNode) || !el.parentNode)
			   		return;
				if (L.isUndefined(el.parentNode.tagName) || !el.parentNode.tagName)
					return;

				if (!bEnable) el.blur();
				el.disabled = !bEnable;
			});
		}

		var sAddClass = bEnable ? sEnableClass : sDisableClass;
		var sRemoveClass = bEnable ? sDisableClass : sEnableClass;
		this.ReplaceClass(sRemoveClass, sAddClass);
	},

	/**
	 * Disables the widget. This just calls this.Enable(false), so you normally don't need to override this if you're already providing Enable.
	 * @param {String} sEnableClass if set, this class is added to an enabled widget, and removed from a disabled one
	 * @param {String} sDisableClass if set, this class is removed from an enabled widget, and added to a disabled one
	 */
	Disable: function(sEnableClass, sDisableClass)
	{
		this.Enable(false, sEnableClass, sDisableClass);
	},

	/**
	 * Attrib method to handle the "Disabled" attrib (calls the Enable method)
	 */
	AttribMethod_Disabled: function(bValue)
	{
		this.Enable(!bValue);
	},

	/**
	 * Shows / hides the widget
	 * @param {Boolean} bShow if false, then this method hides the widget
	 */
	Show: function(bShow)
	{
		if (L.isUndefined(bShow))
			bShow = true;

		if (bShow)
		{
			Dom.removeClass(this.elContainer, 'hide');
			if (this.GetAttribDefault('Overlabel'))
				this._ShowOverlabel(this.GetValue() === '');
		}
		else
		{
			Dom.batch(this.elContainer, function(el)
			{
				el.blur();
				Dom.addClass(el, 'hide');
			});
			this._ShowOverlabel(false);
		}

		U.ForEach(this.aChildWidgets, function(oWidget) { oWidget.Show(bShow); });
	},

	/**
	 * Hides the widget. This just calls this.Show(false), so you normally don't need to override this if you're already providing Show.
	 */
	Hide: function()
	{
		this.Show(false);
	},

	/**
	 * Sets up the widget for "overlabels", where CSS is used to overlay the label on the element until it's focussed or non-empty
	 */
	AttribMethod_Overlabel: function(bEnabled)
	{
		var elLabel, bShow;

		if (bEnabled)
		{
			if (this._elInput && !L.isArray(this._elInput) && (elLabel = this.GetLabelEl()))
			{
				Dom.addClass(elLabel, 'zcOverlabel');
				this._ShowOverlabel(this.GetValue() === '' && this.IsVisible()); // set initial styles

				// Set handlers to show and hide labels.
				this.AddEvent(function() { this._ShowOverlabel(false); }, 'focus', this);
				this.AddEvent(function() { if (this.GetValue() === '') this._ShowOverlabel(true); }, 'blur', this);

				// Handle clicks to LABEL elements (for Safari).
				if (YAHOO.env.ua.webkit)
				{
					Evt.on(elLabel, 'click', function () { this.focus(); }, this._elInput, true);
				}
			}
			else
			{
				this.SetAttrib('Overlabel', false);
			}
		}
		else
		{
			Dom.removeClass(elLabel, 'zcOverlabel');
			Dom.setStyle(elLabel, 'text-indent', '0px');
		}
	},

	/**
	 * Handles the show/hide of overlabels
	 * @private
	 */
	_ShowOverlabel: function(bShow)
	{
		var elLabel = this.GetLabelEl(), oRegion, aXY;
		if (L.isUndefined(bShow))
			bShow = true;

		if (bShow)
		{
			oRegion = Dom.getRegion(this._elInput);

			aXY = [oRegion.x + 5, oRegion.y];
			if (!YAHOO.env.ua.ie)
				aXY[1] += 5;

			Dom.setXY(elLabel, aXY);
		}
		Dom.setStyle(elLabel, 'text-indent', (bShow) ? '0px' : '-4000px');
	},
		
	/**
	 * Returns the visibility of the widget.
	 * @return boolean true if the widget is visible
	 */
	IsVisible: function()
	{
		return !(Dom.hasClass(this._elInput, 'hide') || Dom.hasClass(this.elContainer, 'hide'));
	},

	/**
	 * Adds the given class to the widget's HTML elements - the container, and the input
	 *
	 * @param {String} sClassName class to add
	 */
	AddClass: function(sClassName)
	{
		if (this.elContainer._zcRefCount == 1)
			Dom.addClass(this.elContainer, sClassName);
		if (this._elInput)
			Dom.addClass(this._elInput, sClassName);
	},

	/**
	 * Removes the given class from the widget's HTML elements - the container, and the input
	 *
	 * @param {String} sClassName class to add
	 */
	RemoveClass: function(sClassName)
	{
		if (this.elContainer._zcRefCount == 1)
			Dom.removeClass(this.elContainer, sClassName);
		if (this._elInput)
			Dom.removeClass(this._elInput, sClassName);
	},

	/**
	 * Replaces one class with another on the widget's HTML elements - the container, and the input
	 * If the old class isn't on the element already, it just adds the new class
	 *
	 * @param {String} sClassName class to add
	 * @returns {Array} array of booleans indicating success/failure, the first index refers to the container, the second to the input
	 */
	ReplaceClass: function(sOldClass, sNewClass)
	{
		if (sNewClass)
		{
			if (this.elContainer._zcRefCount == 1)
				Dom.replaceClass(this.elContainer, sOldClass, sNewClass);
			if (this._elInput)
				Dom.replaceClass(this._elInput, sOldClass, sNewClass);
		}
		else
		{
			if (this.elContainer._zcRefCount == 1)
				Dom.removeClass(this.elContainer, sOldClass);
			if (this._elInput)
				Dom.removeClass(this._elInput, sOldClass);
		}
	},

	/**
	 * Validates the widget. Calls SetValid.
	 * @return {Boolean} true if the widget is valid
	 */
	Validate: function()
	{
		for (var sName in this.aDef.Validation)
		{
			if (L.hasOwnProperty(this.aDef.Validation, sName))
			{
				var oValidator = ZC.JSManager.GetValidator(sName);
				if (!L.isUndefined(oValidator) && !oValidator.Validate(this.GetValue(), this))
				{
					this.SetValid(false, this.aDef.Validation[sName] || oValidator.GetDefaultValidationMsg());
					return false;
				}
			}
		}

		try {
			var bValid = true;
			U.ForEach(this.aChildWidgets, function(oWidget) { 
				if (!oWidget.GetAttribDefault('SkipValidation'))
				{
					bValid = oWidget.Validate() && bValid; 
				}
			});

			this.SetValid(bValid);
			return bValid;
		}
		catch (e)
		{
			YAHOO.log("Error during validation: " + e.message + " (" + e.fileName + ":" + e.lineNumber + ")", "warn", this.sName); 
		}
	},

	/**
	 * Returns the validation status of this widget. It will run the validators iff they haven't been run already since the widget was last changed.
	 * @return {Boolean} true if the widget is valid
	 */
	IsValid: function()
	{
		if (L.isUndefined(this._bIsValid))
			return this.Validate();

		return this._bIsValid;
	},

	/**
	 * Sets the widget's validation status, with optional message.
	 * @param {Boolean} bIsValid validation status of the widget
	 * @param {String} sValMsg optional validation message
	 */
	SetValid: function(bIsValid, sValMsg)
	{
		var sOldClass, sNewClass, elValMsg;

		this._bIsValid = bIsValid;
		this.sValidationMessage = sValMsg;

		if (L.isUndefined(this.aDef.ShowValidationStatus) || this.aDef.ShowValidationStatus)
		{
			if (this.elContainer)
			{
				var sOldClass = bIsValid ? 'invalid' : 'valid';
				var sNewClass = bIsValid ? 'valid' : 'invalid';
				Dom.replaceClass(this.elContainer, sOldClass, sNewClass);
			}

			if (this.elValidationContainer)
			{
				if (!bIsValid && sValMsg)
				{
					elValMsg = document.createTextNode(sValMsg);

					if (this.elValidationContainer.firstChild)
						this.elValidationContainer.replaceChild(elValMsg, this.elValidationContainer.firstChild);
					else
						this.elValidationContainer.appendChild(elValMsg);

					Dom.removeClass(this.elValidationContainer, 'hide');
				}
				else
				{
					Dom.addClass(this.elValidationContainer, 'hide');
				}
			}
		}
	},

	/**
	 * Returns the validation message
	 * @return {String} the validation message set on the widget
	 */
	GetValidationMsg: function()
	{
		return this.sValidationMessage;
	},


	/**
	 * Returns the element(s) to add events to.
	 * @return {Object/Array} either an element, or array of elements.
	 */
	GetEventElement: function()
	{
		return this._elInput;
	},

	/**
	 * Adds a child widget from a definition
	 * @param {String} sName the widget name
	 * @param {Array} aDef the widget definition
	 */
	AddChildWidget: function(sName, aDef)
	{
		this.aChildWidgets[sName] = ZC.Core.Widget.NewFromDef(sName, aDef, this.oForm, this);
	},

	/**
	 * Removes a child widget
	 * @param {String} sName the widget name
	 */
	RemoveChildWidget: function(sName)
	{
		this.aChildWidgets[sName].Destruct();
		delete(this.aChildWidgets[sName]);
	},

	/**
	 * Check for child widgets
	 * @returns true if this widget has children
	 */
	HasChildWidgets: function()
	{
		for (var sName in this.aChildWidgets)
		{
			if (L.hasOwnProperty(this.aChildWidgets, sName))
				return true;
		}
		return false;
	},

	/**
	 * Returns the block which contains this widget
	 * @returns {Object} the block that contains this widget, or <code>undefined</code> if not found.
	 */
	FindContainingBlock: function()
	{
		if (this.oParent instanceof ZC.Core.Block)
		{
			return this.oParent;
		}
		else if (!L.isUndefined(this.oForm))
		{
			return this.oForm.FindContainingBlock();
		}
		else if (!L.isUndefined(this.oParent))
		{
			return this.oParent.FindContainingBlock();
		}
		else
		{
			return undefined;
		}
	},

	/**
	 * Check for a validator
	 * @param {String} sName validator name
	 * @returns true if the named validator exists on this widget
	 */
	HasValidator: function(sName)
	{
		return !L.isUndefined((this.GetAttribDefault('Validation', {}))[sName]);
	},

	/** 
	 * @private 
	 */
	_aSearchObjects: [ 'aChildWidgets' ]
}
L.augment(ZC.Core.Widget, AttribProvider);
L.augment(ZC.Core.Widget, GetWidgetProvider);
L.augment(ZC.Core.Widget, EventProvider);

/**
 * Represents a Form, which is just a specialised widget.
 *
 * @class Form
 * @constructor
 * @namespace ZC.Core
 * @extends ZC.Core.Widget
 * @param {String} sName form name
 * @param {Array} aDef an associative array containing the form definition
 * @param {Object} oParent the parent of this form, if available
 */
ZC.Core.Form = function(sName, aDef, oParent)
{
	if (L.isUndefined(aDef.ShowValidationStatus))
		aDef.ShowValidationStatus = false;

	ZC.Core.Form.superclass.constructor.call(this, sName, aDef, this, oParent);
	
	var fnSubmitHandler = function(oEvent)
	{
		var oSelectedEndWidget = this.GetSelectedEndWidget();

		if (this.GetAttribDefault('ValidateOnSubmit') && (!oSelectedEndWidget || !oSelectedEndWidget.GetAttribDefault('SkipValidation')) && !this.IsValid())
		{
			this.SetSelectedEndWidget(undefined);
			Evt.stopEvent(oEvent);


			ZC.JSManager.Alert(_GT("There are still some errors on the form. Please check and re-submit."));
			return false;
		}

		if (this.GetAttribDefault('SubmitWithAJAX'))
		{
			// prevent the default action, but don't prevent other submit handlers from running..
			Evt.preventDefault(oEvent);

			// delay the AJAX submission until the JS engine is idle (i.e. has finished processing submit handlers)
			L.later(0, this, 'AjaxSubmit');
			this.SetAttrib('SubmitWithAJAX', false); // if for some reason this handler is called again, don't try AJAX a second time.
		}

		return true;
	}

	this.AddEvent(fnSubmitHandler, 'submit', this);
}
L.extend(ZC.Core.Form, ZC.Core.Widget);

ZC.Core.Form.prototype.Destruct = function()
{
	this.CloseAjaxDialog();
	ZC.Core.Form.superclass.Destruct.apply(this, arguments);
}

ZC.Core.Form.prototype.CloseAjaxDialog = function()
{
	if (this._elInput && this._elInput._ajaxDialog)
	{
		ZC.JSManager.DestroyDialog(this._elInput._ajaxDialog);
		this._elInput._ajaxDialog = null;
	}
}

ZC.Core.Form.prototype._WidgetNameToID = function()
{
	return this.aDef.ID || this.sName;
}
/**
 * Enables/disables all of the widgets on the form
 * @param {Boolean} bEnable if false, then this method disables the widget
 * @param {String} sEnableClass if set, this class is added to an enabled widget, and removed from a disabled one
 * @param {String} sDisableClass if set, this class is removed from an enabled widget, and added to a disabled one
 */
ZC.Core.Form.prototype.Enable = function ()
{
	var aArgs = arguments;
	U.ForEach(this.aChildWidgets, function(oWidget) { oWidget.Enable.apply(oWidget, aArgs); })
}
/**
 * Clears all the widgets on the form
 */
ZC.Core.Form.prototype.Clear = function ()
{
	var aArgs = arguments;
	U.ForEach(this.aChildWidgets, function(oWidget) { oWidget.Clear.apply(oWidget, aArgs); })
}

// Forms always check child widgets
ZC.Core.Form.prototype.IsValid = function()
{
	return this.Validate();
}

ZC.Core.Form.prototype.Validate = function()
{
	try {
		var aValidation = this.GetAttribDefault('FormValidation', {}),
			bValid = true, sValidationMessage;

		U.ForEach(aValidation, function(oFormValParams, sName)
		{
			var oValidator = ZC.JSManager.GetFormValidator(sName);
			if (!L.isUndefined(oValidator) && !oValidator.Validate(oFormValParams, this))
			{
				bValid = false;
				sValidationMessage = this.GetAttribDefault('FormValidationMsg', oValidator.GetDefaultValidationMsg());
			}
		}, this);

		U.ForEach(this.aChildWidgets, function(oWidget) { 
			if (oWidget.GetAttribDefault('__IsField')) // only validate widgets that are also fields
				bValid = oWidget.Validate() && bValid; 
		});

		this.SetValid(bValid, sValidationMessage);
		return bValid;
	}
	catch (e)
	{
		YAHOO.log("Error during validation: " + e.message + " (" + e.fileName + ":" + e.lineNumber + ")", "warn", this.sName); 
		return false;
	}
}

/**
 * Submits this form via AJAX. Set the attrib SubmitWithAJAX when creating the
 * form to have this done automatically when the submit button is clicked.
 */
ZC.Core.Form.prototype.AjaxSubmit = function()
{
	if (!this.oParent || !this.oParent.AjaxRequest)
		return;
		
	var fnAjaxReset = function()
	{
		this.SetAttrib('SubmitWithAJAX', true);
		var aSubmitWidgets = this.GetWidgetsBy(function(o) { return o instanceof ZC.Core.Widget.Submit; });
		U.ForEach(aSubmitWidgets, function(oWidget)
		{
			oWidget.SetAttrib('value', oWidget.GetAttribDefault('OldValue', oWidget.GetAttrib('value')));
			oWidget.Enable();
		});
	};

	this.oParent.AjaxRequest({ Form: this._elInput, OnComplete:fnAjaxReset, Scope:this });
	this.CloseAjaxDialog();

	var aSubmitWidgets = this.GetWidgetsBy(function(o) { return o instanceof ZC.Core.Widget.Submit; });
	U.ForEach(aSubmitWidgets, function(oWidget)
	{
		oWidget.SetAttrib('OldValue', oWidget.GetValue());
		oWidget.SetAttrib('value', _GT('Please wait...'));
		oWidget.Disable();
	});
}


/**
 * Adds a hidden field to the form.
 * @param {String} sName the name of the hidden field
 * @param {String/Array/Object} mValue the value to set the field to
 * @return {HTMLElement} the created element
 */
ZC.Core.Form.prototype.AddHiddenField = function(sName, mValue)
{
	var aResult = [],
		fnAddHidden = function(sName, sValue)
		{
			var elHidden = document.createElement('input');
			elHidden.type = 'hidden';
			elHidden.name = sName;
			elHidden.value = sValue;
			this.appendChild(elHidden);
			return elHidden;
		};

	if (L.isString(mValue))
	{
		return fnAddHidden.call(this._elInput, sName, mValue);
	}
	else if (L.isArray(mValue) || L.isObject(mValue))
	{
		U.ForEach(mValue, function(sVal, sKey) {
			aResult.push(fnAddHidden.call(this, sName + '[' + sKey + ']', sVal));
		}, this._elInput);
		return aResult;
	}

	return undefined;
}

/**
 * Sets the selected end widget for this form.
 *
 * It's not actually that easy to find out which button submitted a form from
 * an "on submit" handler, so we need to keep track of it ourselves using click
 * events on the widgets. Any widget which submits the form should attach an
 * event that calls this method before the submit handlers run.
 *
 * @param {ZC.Core.Widget} oSelectedEndWidget the widget that submitted the form
 */
ZC.Core.Form.prototype.SetSelectedEndWidget = function(oSelectedEndWidget)
{
	this.oSelectedEndWidget = oSelectedEndWidget;
}

/**
 * Gets the selected end widget for this form.
 *
 * @return {ZC.Core.Widget} the most recent widget that tried to submit the form.
 */
ZC.Core.Form.prototype.GetSelectedEndWidget = function()
{
	return this.oSelectedEndWidget;
}

/**
 * Focusses the first visible, enabled input element on this form.
 */
ZC.Core.Form.prototype.FocusFirstElement = function()
{
	var fnFilter = function(el)
	{
		return el.tagName && U.InArray(el.tagName.toLowerCase(), ['input', 'select', 'textarea']) && el.type != 'hidden' && !el.disabled;
	}
	var aChildNodes = Dom.getElementsBy(fnFilter, false, this._elInput);

	if (aChildNodes.length == 0)
		return;

	if (U.Some(aChildNodes, function(elNode) { return (elNode.tabIndex && elNode.tabIndex > 0); }))
	{
		var elLowestTabIndex;
		U.ForEach(aChildNodes, function(elNode)
		{
			if (!elLowestTabIndex || elLowestTabIndex.tabIndex > elNode.tabIndex)
				elLowestTabIndex = elNode;
		});
		elLowestTabIndex.focus();
	}
	else
		aChildNodes[0].focus();
}

/**
 * Base class for all validators. Validators are singleton objects.
 * @class Validator
 * @constructor
 * @namespace ZC.Core
 * @uses ZC.AttribProvider
 */
ZC.Core.Validator = function()
{
	this.AttribProviderSetup();
}

/**
 * This creates a validator class and extends the base validator. Creates the required namespace if it doesn't already exist.
 * @param {String} sName the validator name
 * @param {String} sModule the module for this validator (defaults to Core)
 * @return {Object} the validator object
 */
ZC.Core.Validator.Create = function(sName, sModule)
{
	sModule = sModule || 'Core';
	var oNS = ZC.Namespace(sModule + '.Validator');

	oNS[sName] = function() {};
	L.extend(oNS[sName], ZC.Core.Validator);
	oNS[sName].sClassName = sModule + '_Validator_' + sName;

	return oNS[sName];
}

ZC.Core.Validator.prototype = {
	/**
	 * Returns the default validation message, used if the validator def has no custom message.
	 * @return {String} validation message
	 */
	GetDefaultValidationMsg: function()
	{
		return this.sDefaultValidationMessage || _GT("Invalid value");
	},

	/**
	 * Validates the given value.
	 * @param Value Widget value
	 * @param {Object} oWidget The widget we're validating.
	 */
	Validate: function(Value, oWidget)
	{
		if (L.isUndefined(this.oValidationRegex))
		{
			//YAHOO.log('oValidationRegex not defined, and Validate not overridden', "error", this.sClassName);
			return undefined;
		}

		if (Value instanceof Array)
		{
			for (var Key in Value)
			{
				if (L.hasOwnProperty(Value, Key) && !this.oValidationRegex.test(Value[Key]))
					return false;
			}

			return true;
		}
		else
			return this.oValidationRegex.test(Value);
	}
}
L.augment(ZC.Core.Validator, AttribProvider);

/**
 * Base class for event listeners. The constructor takes a (destination) widget
 * object and an event object, and subscribes to the event.
 *
 * @class EventListener
 * @constructor
 * @namespace ZC.Core
 * @uses ZC.AttribProvider
 * @param {Object} oDestination the destination object (can be a widget or a block)
 * @param {Object} oEvent the custom event to subscribe to
 * @param {Object} aDef the event listener definition
 */
ZC.Core.EventListener = function(oDestination, oEvent, aDef)
{
	/**
	 * The destination object (can be a widget or a block)
	 */
	this.oDestination = oDestination;

	/**
	 * This is the same as <code>oDestination</code>, for backwards compatibility.
	 * @deprecated
	 */
	this.oDestWidget = this.oDestination;

	/**
	 * The custom event to subscribe to
	 */
	this.oEvent = oEvent;
	/**
	 * The listener definition
	 */
	this.aDef = aDef;

	if (this.Setup())
	{
		oEvent.subscribe(this._EventHandler, this, this);
	}
	else
	{
		throw new Error('Setup returned false');
	}
	
	this.AttribProviderSetup();
}

/**
 * This static method creates an eventlistener class and extends the base
 * class. Creates the required namespace if it doesn't already exist.
 *
 * @static
 * @param {String} sName the eventlistener name
 * @param {String} sModule the module for this eventlistener (defaults to Core)
 * @return {Object} the eventlistener object
 */
ZC.Core.EventListener.Create = function(sName, sModule)
{
	sModule = sModule || 'Core';
	var oNS = ZC.Namespace(sModule + '.EventListener');

	oNS[sName] = function(oDestination, oEvent, aDef)
	{
		oNS[sName].superclass.constructor.call(this, oDestination, oEvent, aDef);
	}
	L.extend(oNS[sName], ZC.Core.EventListener);

	return oNS[sName];
}

ZC.Core.EventListener.prototype = {
	/**
	 * Optional setup method
	 * @return {Boolean} if false, then the EL will not subscribe to the event
	 */
	Setup: function()
	{
		return true;
	},

	/**
	 * Frees up all the references in this object. Called on page unload to avoid memory leaks in IE.
	 */
	Destruct: function()
	{
		YAHOO.log("destructing eventlistener for " + (this.oEvent ? this.oEvent.type : '(null event)') + " on " + (this.oDestination ? this.oDestination.sName : "(null destination)"), "debug", "EventListener::Destruct");

		if (this.oEvent)
			this.oEvent.unsubscribe(this._EventHandler, this);

		this.sDestName = this.oDestination.sName;
		this.oDestination = null;
		this.oDestWidget = null;
		this.oEvent = null;
	},

	/**
	 * translates the parameters we get from YUI
	 * @private
	 * @param {String} sEventName Custom event that fired
	 * @param {Array} aArgs arguments passed to fire, 0 = widget, 1 = browser source event
	 */
	_EventHandler: function(sEventName, aArgs)
	{
		var oEvent = ZC.JSManager.GetEvent(sEventName),
		    oWidget = aArgs[0],
		    oSrcEvent = aArgs[1];

		if (this.oDestination && this.oEvent)
		{
			this.HandleEvent(oWidget, oEvent, oSrcEvent);
		}
	},

	/**
	 * Tests the given value against a single or list of equals / not equals values.
	 * <ul>
	 * <li>if the Equals list/value is not null, returns true if it contains the value,</li
	 * <li>if the NotEquals list/value is not null, returns true if it does not contain the value.</li>
	 * </ul>
	 *
	 * @protected
	 * @param {String|Array} Value the value to search for
	 * @param {String|Array} Equals the equals list or value
	 * @param {String|Array} NotEquals the not-equals list or value
	 * @return {Boolean} true if the above conditions match, otherwise false
	 */
	_SearchLists: function(Value, Equals, NotEquals)
	{
		var fnCompare = function(Value, CompareValues, bReturnIfMatch)
		{
			if (L.isArray(CompareValues))
			{
				// not using InArray as we want non-strict equality checking
				for (var mVal in CompareValues)
				{
					if (L.hasOwnProperty(CompareValues, mVal) && Value == CompareValues[mVal])
						return bReturnIfMatch;
				}
				return !bReturnIfMatch;
			}
			else
				return (Value == CompareValues) ? bReturnIfMatch : !bReturnIfMatch;
		}
		var bResult = false;

		if (!L.isUndefined(Equals) && !L.isNull(Equals))
			bResult = fnCompare(Value, Equals, true);

		if (!L.isUndefined(NotEquals) && !L.isNull(NotEquals))
			bResult = bResult || fnCompare(Value, NotEquals, false);

		return bResult;
	},

	/**
	 * Gets the value of the given widget. By default, it calls GetValue, but if the attrib GetValueMethod is set it calls that method instead.
	 * @protected
	 * @param {Object} oWidget the widget to get the value from
	 * @return the widget value
	 */
	_GetWidgetValue: function(oWidget)
	{
		return (oWidget[this.GetAttribDefault('GetValueMethod', 'GetValue')])();
	},

	/**
	 * Event handler, called when the event fires.
	 * @param {Object} oWidget the widget that the event fired on
	 * @param {Object} oEvent the custom event that fired
	 * @param {Object} oSrcEvent the browser event that caused this event to fire (if available)
	 */
	HandleEvent: function(oWidget, oEvent, oSrcEvent)
	{
		throw new Error('EventListener::HandleEvent is abstract and must be overridden');
	}
}
L.augment(ZC.Core.EventListener, AttribProvider);

/**
 * Base class for CSOs. The constructor takes a def and sets it on the object.
 * 
 * @class ClientSideObject
 * @constructor
 * @namespace ZC.Core
 * @uses ZC.AttribProvider
 * @param {Object} aDef the CSO definition
 */
ZC.Core.ClientSideObject = function(aDef)
{
	/**
	 * The CSO definition
	 */
	this.aDef = aDef;

	this.Setup();
	
	this.AttribProviderSetup();
}

/**
 * This static method creates an eventlistener class and extends the base
 * class. Creates the required namespace if it doesn't already exist.
 *
 * @static
 * @param {String} sName the eventlistener name
 * @param {String} sModule the module for this eventlistener (defaults to Core)
 * @return {Object} the eventlistener object
 */
ZC.Core.ClientSideObject.Create = function(sName, sModule)
{
	sModule = sModule || 'Core';
	var oNS = ZC.Namespace(sModule + '.ClientSideObject');

	oNS[sName] = function(aDef)
	{
		oNS[sName].superclass.constructor.call(this, aDef);
	}
	L.extend(oNS[sName], ZC.Core.ClientSideObject);

	return oNS[sName];
}

ZC.Core.ClientSideObject.prototype = {
	/**
	 * Setup method
	 */
	Setup: function()
	{
	},

	/**
	 * Destructor
	 */
	Destruct: function()
	{
	}
};
L.augment(ZC.Core.ClientSideObject, AttribProvider);

// end the private scoping
})();
