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.
     * @namespace Contains all Zedcore client-side code
     * @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
 * @static
 * @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);
}

var sprintfRegex = /%%|%(\d+\$)?([-+#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuidfegEG])/g;
/**
 * @namespace Utility functions.
 */
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;
	 },
	
	

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

	/**
	 * Wraps the contents of an existing element in a <div> tag, to make
	 * animation easier (table cells have issues).
	 *
	 * e.g. <td><strong>Hello world</strong></td> -> <td><div><strong>Hello world</strong></div></td>
	 *
	 * @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)
		{
			oAnim = new YAHOO.util.Scroll(document.body, { scroll: { to : aToXY }  }, fDuration, fnEasing);
			oAnim.animate();  
		}
	},

	/* * * * 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.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);
	},

	/* * * * 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;
	}
}

var U = ZC.Util;

// Generic GetAttrib implementation, which we mix in to the objects below
var _GetAttrib = function(sName)
{
	if (this.AttribIsset(sName))
	{
		return this.aDef[sName];
	}
	else
	{
		YAHOO.log('Attrib ' + sName + ' is not set', 'error', this.sName + '::GetAttrib');
	}
}

// Generic GetAttribDefault implementation, which we mix in to the objects below
var _GetAttribDefault = function(sName, mDefault)
{
	if(L.isUndefined(mDefault))
		mDefault = false;

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

// Generic AttribIsset implementation, which we mix in to the objects below
var _AttribIsset = function(sName)
{
	return !L.isUndefined(this.aDef[sName]);
}

// Generic AttribsAreSet implementation, which we mix in to the objects below
var _AttribsAreSet = function(aAttribs)
{
	return U.Every(aAttribs, _AttribIsset, this);
}

// Generic GetWidget implementation, which we mix in to the objects below
var _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))
			{
				oChildSearch = oSearchObject[sName].GetWidget(sSearch);
				if (!L.isUndefined(oChildSearch))
					return oChildSearch;
			}
		}
	}

	return undefined;
}

var _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;
}

/**
 * @name ZC.Core
 * @namespace CMS Core namespace
 */
ZC.Namespace('Core');

/**
 * @class The manager object, responsible for initialising all the JS widgets and events on the page.
 */
ZC.JSManager = {
	aClientSideObjects: {},
	aEvents: {},
	aForms: {},
	aBlocks: {},
	aValidators: {},

	/**
	 * 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.
	 *
	 * @member ZC.JSManager
	 * @param {Object} oConfig
	 */
	Init: function(oConfig, bIframeInit)
	{
		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;
		}

		this.oConfig = oConfig;
		if (YAHOO.widget.Logger)
		{
			// if Logger is enabled then we're debugging, so we also tell the YUI event code to re-throw exceptions that occur during event handling
			Evt.throwErrors = true;

			if (window.console && console.log)
				YAHOO.widget.Logger.enableBrowserConsole();
			else
			{
				Evt.onDOMReady(function() {
					var myContainer = document.createElement("div");
					document.body.appendChild(myContainer);
					new YAHOO.widget.LogReader(myContainer);
				});
			}
		}
		//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');

			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.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.fire();
		}, null, this);
	},

	/**
	 * 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 };
					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);
				}
				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()
	{
		/** @private */
		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;
	},

	/**
	 * 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)
			sFirstPart = sCmpName.substring(0, iFirstPos);

		iSecondPos = sCmpName.indexOf('_', iFirstPos + 1);
		if (iSecondPos)
			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)]);

		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(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(", ") + ")", "warning", "ZC.JSManager#GetComponent");
		return undefined;
	},

	/**
	 * 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].aDef.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;
	},

	/**
	 * 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.
	 *
	 * @function
	 * @param {String} sSearch widget name
	 * @return {Object} widget object, or <em>undefined</em> if the widget can't be found
	 */
	GetWidget: _GetWidget,

	/**
	 * 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: _GetWidgetsBy,

	/** @ignore */
	_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
	 */
	GetValidator: function(sName)
	{
		if (!L.isUndefined(this.aValidators[sName]))
			return this.aValidators[sName];

		if (L.isUndefined(this.oValidatorRegex))
			this.oValidatorRegex = /^(.*)_Validator_(.*)$/;

		var aMatches = this.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 (this.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 (this.aValidators[sName] = new ZC[aMatches[1]].Validator[aMatches[2]]());
		}
	},

	/**
	 * Constructs a URL from an array of parameters.
	 * @param {Object} oParams Query string parameters
	 * @param {String} [sURL] Optional URL. Uses the current location if not specified
	 * @return {String} a URL
	 */
	URL: function(oParams, sURL)
	{
		oParams = oParams || {};
		sURL = sURL || window.location.href.replace(/(\?|#).*/,'');

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

		var iQSPos = sURL.indexOf('?');
		if (iQSPos > -1)
		{
			var aTmpParams = sURL.substr(iQSPos + 1).split(/&/);
			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] || '';
			});
			sURL = sURL.substr(0, iQSPos);
		}

		if (this.oConfig.StateID)
			oParams['_ts'] = this.oConfig.StateID;

		var aParamParts = [];
		U.ForEach(oParams, function(sValue, sKey)
		{
			// values can be widgets
			if (L.isObject(sValue) && sValue.GetValue)
				sValue = sValue.GetValue();

			aParamParts.push(encodeURIComponent(sKey) + '=' + encodeURIComponent(sValue));
		});

		sURL = sURL + '?' + aParamParts.join('&');
		return sURL;
	},

	/**
	 * 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.
	 * @param {String} sMessage alert message
	 * @param {Function} fnOKHandler (optional) handler to run when the user confirms
	 */
	Alert: function(sMessage, fnOKHandler)
	{
		if (L.isUndefined(fnOKHandler))
			fnOKHandler = function(){};

		if (!L.isUndefined(YAHOO.widget.SimpleDialog))
		{
			var oPanel = new YAHOO.widget.SimpleDialog("alert",
			{
				width: "300px",
				fixedcenter: true,
				visible: false,
				draggable: false,
				close: false,
				modal: true,
				text: sMessage,
				icon: YAHOO.widget.SimpleDialog.ICON_ALARM,
				constraintoviewport: true,
				buttons: [ { text: "OK", handler: function() { this.hide(); fnOKHandler(); }, isDefault: true } ]
			});
			oPanel.render(document.body);
			oPanel.show();
		}
		else
		{
			alert(sMessage);
			fnOKHandler();
		}
	},

	/**
	 * Creates a dialog box for a widget, and registers it with the overlay manager.
	 *
	 * @param {String} sCaption the window caption
	 * @param {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;

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

		elDialogBody.appendChild(elBody);
		elDialogContainer.appendChild(elDialogHeader);
		elDialogContainer.appendChild(elDialogBody);
		(elContainer || document.body).appendChild(elDialogContainer);

		if (L.isUndefined(oConfig))
			oConfig = {};
		if (L.isUndefined(oConfig.close))
			oConfig.close = true;
		if (L.isUndefined(oConfig.draggable))
			oConfig.draggable = true;
		if (L.isUndefined(oConfig.visible))
			oConfig.visible = false;
		if (L.isUndefined(oConfig.effect) && !L.isUndefined(YAHOO.util.Anim))
			oConfig.effect = { effect:YAHOO.widget.ContainerEffect.FADE, duration:0.25 };

		oDialog = new YAHOO.widget.Dialog(elDialogContainer, oConfig);

		if (L.isUndefined(this.oOverlayManager))
			this.oOverlayManager = new YAHOO.widget.OverlayManager();

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

/**
 * @class Base class for the Block classes.
 * Currently all blocks do is contain widgets, forms and other blocks. We may
 * extend the functionality in future, e.g. to provide AJAX-loading of blocks.
 *
 * @param {String} sName block name
 * @param {Array} aDef block definition
 * @param {Object} oParent parent block
 */
ZC.Core.Block = function(sName, aDef, oParent)
{
	this.sName = sName;
	this.aDef = aDef;
	this.oParent = oParent;
	this.aChildBlocks = {};
	this.aWidgets = {};
	this.aForms = {};

	//YAHOO.log("Calling CustomSetupStart", "debug", this.sName + " constructor");
	this.CustomSetupStart();
	//YAHOO.log("CustomSetupStart done", "debug", this.sName + " constructor");

	if (!L.isUndefined(aDef.Blocks))
	{
		//YAHOO.log("Started initialising child blocks", "debug", this.sName + " constructor");
		U.ForEach(aDef.Blocks, function(aBlockDef, sBlockName)
		{
			this.aChildBlocks[sBlockName] = ZC.Core.Block.NewFromDef(sBlockName, aBlockDef, this);
		}, this);
		//YAHOO.log("Finished initialising child blocks", "debug", this.sName + " constructor");
	}

	if (!L.isUndefined(aDef.Widgets))
	{
		//YAHOO.log("Started initialising widgets", "debug", this.sName + " constructor");
		U.ForEach(aDef.Widgets, function(aWidgetDef, sWidgetName)
		{
			this.aWidgets[sWidgetName] = ZC.Core.Widget.NewFromDef(sWidgetName, aWidgetDef, this);
		}, this);
		//YAHOO.log("Finished initialising widgets", "debug", this.sName + " constructor");
	}

	if (!L.isUndefined(aDef.Forms))
	{
		//YAHOO.log("Started initialising forms", "debug", this.sName + " constructor");
		U.ForEach(aDef.Forms, function(aFormDef, sFormName)
		{
			this.aForms[sFormName] = new ZC.Core.Form(sFormName, aFormDef, this);
			ZC.JSManager.RegisterForm(this.aForms[sFormName]);
		}, this);
		//YAHOO.log("Finished initialising forms", "debug", this.sName + " constructor");
	}

	//YAHOO.log("Calling CustomSetupEnd", "debug", this.sName + " constructor");
	this.CustomSetupEnd();
	//YAHOO.log("CustomSetupEnd done", "debug", this.sName + " constructor");
}

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.
 *
 * @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)
 * @return {Object} the block object
 */
ZC.Core.Block.Create = function(sBlockClassName, sModule, oParentClass)
{
	sModule = sModule || 'Core';
	oParentClass = oParentClass || ZC.Core.Block;
	var oNS = ZC.Namespace(sModule + '.Block');

	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.
	 */
	CustomSetupStart: function()
	{
		// does nothing by default
	},

	/**
	 * Called by the block constructor after the base block class has initialised.
	 */
	CustomSetupEnd: function()
	{
		// does nothing by default
	},

	/**
	 * 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.aChildBlocks)
		{
			if (L.hasOwnProperty(this.aChildBlocks, sName))
			{
				if (this.aChildBlocks[sName].aDef.UniqueID == sUniqueID)
					return this.aChildBlocks[sName];

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

	/**
	 * 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.
	 *
	 * @function
	 * @param {String} sSearch widget name
	 * @return {Object} widget object, or <em>undefined</em> if the widget can't be found
	 */
	GetWidget: _GetWidget,

	/**
	 * 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: _GetWidgetsBy,

	/**
	 * 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: _GetAttribDefault,

	/**
	 * Gets the named attrib.
	 * @param {string} sName attrib to get
	 * @return Value attribute value
	 */
	GetAttrib: _GetAttrib,

	/**
	 * Checks that the named attrib is set
	 * @param {string} sName attrib to check
	 * @return {Boolean} true if the attrib is set
	 */
	AttribIsset: _AttribIsset,

	/**
	 * Checks that the named attribs are set
	 * @param {Array} aAttribs attribs to check
	 * @return {Boolean} true if the attrib is set
	 */
	AttribsAreSet: _AttribsAreSet,

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

/**
 * @class Base class for all the Widget classes.
 *
 * @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)
{
	try {
		YAHOO.log("Started initialising " + sName, "debug", sName + " constructor");
		this.sName = sName;
		this.aDef = aDef;
		this.oParent = oParent;
		this.oForm = oForm;
		this.aChildWidgets = {};

		this._FindElements();
		this.CustomSetupStart();

		if (!L.isUndefined(this.aDef.EventListeners))
		{
			YAHOO.log("Started initialising EventListeners", "debug", sName + " constructor");
			U.ForEach(this.aDef.EventListeners, function(aELDefs, sEventName)
			{
				U.ForEach(aELDefs, function(aELDef) { this.AddEventListener(sEventName, aELDef) }, this);
			}, this);
			YAHOO.log("Finished initialising EventListeners", "debug", sName + " constructor");
		}
		if (!L.isUndefined(this.aDef.Widgets))
		{
			YAHOO.log("Started initialising Widgets", "debug", sName + " constructor");
			U.ForEach(this.aDef.Widgets, function(aWidgetDef, sWidgetName)
			{
				this.AddChildWidget(sWidgetName, aWidgetDef);
			}, this);
			YAHOO.log("Finished initialising Widgets", "debug", sName + " constructor");
		}

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

		if (!L.isUndefined(this.aDef.Events))
		{
			YAHOO.log("Started initialising Events", "debug", sName + " constructor");
			U.ForEach(this.aDef.Events, function(aOn, sEventName)
			{
				this.AddEvent(sEventName, aOn);
			}, this);
			YAHOO.log("Finished initialising Events", "debug", sName + " constructor");
		}
		if (!L.isUndefined(this.aDef.ValidateOnEvents))
		{
			this.AddEvent(this.Validate, this.aDef.ValidateOnEvents, this);
		}

		var elLabel, bShow;
		if (this.GetAttribDefault('Overlabel') && 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);
		}

		this.CustomSetupEnd();
		YAHOO.log("Finished initialising " + sName, "debug", sName + " constructor");
	}
	catch (ex)
	{
		YAHOO.log("Exception initialising widget: " + ex.message + " (" + ex.fileName + ":" + ex.lineNumber + ")", "error", sName + " constructor");
	}
}

/**
 * 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(sName, aDef, oForm, oParent)
	{
		oNS[sWidgetClassName].superclass.constructor.call(this, sName, aDef, oForm, oParent);
	}
	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.
	 */
	CustomSetupStart: function()
	{
		// does nothing by default
	},

	/**
	 * Called by the widget constructor after the base widget class has initialised.
	 */
	CustomSetupEnd: function()
	{
		// does nothing by default
	},

	/**
	 * Works out the element ID from the widget/form/parent name
	 * @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 + '.';

		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.
	 */
	_FindElements: function() {
		var sID = this._WidgetNameToID();

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

	/**
	 * Populates elContainer and elValidationContainer.
	 */
	_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') || 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';
				this.elContainer.appendChild(this.elValidationContainer);
			}
		}

		if (this.IsReadOnly())
		{
			this.elRODisplay = Dom.get(elInput.id + '_rodisplay');
			if (this.elRODisplay)
			{
				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;
	},

	/**
	 * 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');
	},

	/**
	 * Checks the input element for event handlers for the given event and fires any that are registered.
	 * @param {String} sEvent Event name
	 */
	_FireEventHandlers: function(sEvent)
	{
		var elInput = L.isArray(this._elInput) ? this._elInput[0] : this._elInput,
			aHandlers = Evt.getListeners(elInput, sEvent),
			aEventArgs = [], i, iMax;

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

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

	/**
	 * 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 (!L.isUndefined(this.aDef.DisplayAs) && this.aDef.DisplayAs.toLowerCase() == 'readonly');
	},

	/**
	 * 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: _GetAttribDefault,

	/**
	 * Gets the named attrib.
	 * @param {string} sName attrib to get
	 * @return Value attribute value
	 */
	GetAttrib: _GetAttrib,

	/**
	 * Checks that the named attrib is set
	 * @param {string} sName attrib to check
	 * @return {Boolean} true if the attrib is set
	 */
	AttribIsset: _AttribIsset,

	/**
	 * Checks that the named attribs are set
	 * @param {Array} aAttribs attribs to check
	 * @return {Boolean} true if the attrib is set
	 */
	AttribsAreSet: _AttribsAreSet,

	/**
	 * Sets the value of an attribute. This can be overridden in subclasses if the widget needs to react to an attribute change.
	 * @param {String} sName attrib name to set
	 * @param Value new attrib value
	 */
	SetAttrib: function(sName, Value)
	{
		this.aDef[sName] = Value;
	},

	/**
	 * 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 (!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);
	},

	/**
	 * 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);
		}
	},

	/**
	 * 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);
	},

	/**
	 * Handles the show/hide of overlabels
	 * @protected
	 */
	_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;
				}
			}
		}

		var bValid = true;
		U.ForEach(this.aChildWidgets, function(oWidget) { bValid = oWidget.Validate() && bValid; });

		this.SetValid(bValid);
		return bValid;
	},

	/**
	 * 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)
	{
		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)
			{
				this.elValidationContainer.innerHTML = sValMsg || '';
				Dom[sValMsg ? 'removeClass' : 'addClass'](this.elValidationContainer, 'hide');
			}
		}
	},

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

	/**
	 * Adds event handlers to this widget that fire the custom event sEventName.
	 * @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} oWidget (internal use) the widget to pass to the event handler
	 * @return {Object} the custom event object
	 */
	AddEvent: function(EventNameOrFunction, aOn, oScope, oWidgetOverride)
	{
		if (!this._elInput && !this.HasChildWidgets())
		{
			YAHOO.log('this._elInput is undefined, no child widgets and AddEvent is not overridden', "error", this.sName + '#AddEvent');
			return undefined;
		}

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

		U.ForEach(this.aChildWidgets, function(oWidget) { oWidget.AddEvent(EventNameOrFunction, aOn, oScope, oWidgetOverride) }, this);

		if (!this._elInput)
			return;

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

		var fnFireEvent, oEvent;
		if (typeof EventNameOrFunction == 'function')
		{
			fnFireEvent = EventNameOrFunction;
		}
		else
		{
			if (EventNameOrFunction instanceof YAHOO.util.CustomEvent)
				oEvent = EventNameOrFunction;
			else
				oEvent = ZC.JSManager.GetEvent(EventNameOrFunction);

			fnFireEvent = function(oSrcEvent) { oEvent.fire(oWidgetOverride, oSrcEvent); };
		}

		U.ForEach(aOn, function(sEvent)
		{
			// special case for load event. we run this from the AfterManagerInit event once everything's been initialised (even stuff that inits in ManagerInit).
			if (sEvent == 'load')
			{
				ZC.JSManager.GetEvent('AfterManagerInit').subscribe(function()
				{
					fnFireEvent.call(oScope);
					if (!L.isUndefined(oEvent))
					{
						// if we've been passed a custom event, subscribe to that event's subscribeEvent to catch eventlisteners that subscribe later
						oEvent.subscribeEvent.subscribe(function(sType, aArgs, oObj)
						{
							var fnEventHandler = aArgs[0];
							var aEHArgs = [oWidgetOverride, aArgs[1]];
							var oOverride = aArgs[2];

							if (oOverride === true)
								oOverride = oObj;

							fnEventHandler.call(oOverride, oEvent.type, aEHArgs);
						});
					}
				});
			}
			else
			{
				if (!Evt.addListener(this._elInput, sEvent, fnFireEvent, this, oScope || true))
					YAHOO.log('addListener returned false for ' + sEvent, "warn", this.sName + "#AddEvent");
			}
		}, this);

		return oEvent;
	},

	/**
	 * 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)
	{
		var oEvent = ZC.JSManager.GetEvent(sEventName);
		var fnEventListenerConstructor = ZC.JSManager.GetComponent(aDef.EventListenerType, aDef.Module || 'Core', 'EventListener');
		if (fnEventListenerConstructor)
			return new fnEventListenerConstructor(this, oEvent, aDef);
		else
			YAHOO.log("Unable to load event listener: " + (aDef.Module || 'Core') + "/" + aDef.EventListenerType, "error");
	},

	/**
	 * 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);
	},

	/**
	 * 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;
	},

	/**
	 * 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]);
	},

	/**
	 * 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.
	 *
	 * @function
	 * @param {String} sSearch widget name
	 * @return {Object} widget object, or <em>undefined</em> if the widget can't be found
	 */
	GetWidget: _GetWidget,

	/**
	 * 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: _GetWidgetsBy,

	/** @ignore */
	_aSearchObjects: [ 'aChildWidgets' ]
}

/**
 * @class Represents a Form, which is just a specialised widget.
 *
 * @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);

	if (this.aDef.ValidateOnSubmit)
		this.AddEvent(this._SubmitCheckValid, 'submit', this);
}
L.extend(ZC.Core.Form, ZC.Core.Widget);

ZC.Core.Form.prototype._WidgetNameToID = function()
{
	return this.aDef.ID || this.sName;
}
ZC.Core.Form.prototype.Enable = function ()
{
	var aArgs = arguments;
	U.ForEach(this.aChildWidgets, function(oWidget) { oWidget.Enable.apply(oWidget, aArgs); })
}
ZC.Core.Form.prototype.Clear = function ()
{
	var aArgs = arguments;
	U.ForEach(this.aChildWidgets, function(oWidget) { oWidget.Clear.apply(oWidget, aArgs); })
}

/**
 * Checks that the form is valid. If not, then it blocks the form submission.
 */
ZC.Core.Form.prototype._SubmitCheckValid = function(oEvent)
{
	if (this.IsValid())
		return;

	Evt.stopEvent(oEvent);
	ZC.JSManager.Alert(U.GetText("There are still some errors on the form. Please check and re-submit."));
}
// Forms always check child widgets
ZC.Core.Form.prototype.IsValid = function()
{
	return this.Validate();
}

/**
 * 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();
}

/**
 * @class Base class for all validators. Validators are singleton objects.
 */
ZC.Core.Validator = function()
{
}

/**
 * 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 || U.GetText("Invalid value");
	},

	/**
	 * 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: _GetAttribDefault,

	/**
	 * Gets the named attrib.
	 * @param {string} sName attrib to get
	 * @return Value attribute value
	 */
	GetAttrib: _GetAttrib,

	/**
	 * Checks that the named attrib is set
	 * @param {string} sName attrib to check
	 * @return {Boolean} true if the attrib is set
	 */
	AttribIsset: _AttribIsset,

	/**
	 * Checks that the named attribs are set
	 * @param {Array} aAttribs attribs to check
	 * @return {Boolean} true if the attrib is set
	 */
	AttribsAreSet: _AttribsAreSet,

	/**
	 * 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);
	}
}

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

	if (this.Setup())
		oEvent.subscribe(this._EventHandler, null, this);
}

/**
 * This static method creates an eventlistener class and extends the base
 * class. Creates the required namespace if it doesn't already exist.
 *
 * @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(oDestWidget, oEvent, aDef)
	{
		oNS[sName].superclass.constructor.call(this, oDestWidget, 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;
	},

	/**
	 * 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: _GetAttribDefault,

	/**
	 * Gets the named attrib.
	 * @param {string} sName attrib to get
	 * @return Value attribute value
	 */
	GetAttrib: _GetAttrib,

	/**
	 * Checks that the named attrib is set
	 * @param {string} sName attrib to check
	 * @return {Boolean} true if the attrib is set
	 */
	AttribIsset: _AttribIsset,

	/**
	 * Checks that the named attribs are set
	 * @param {Array} aAttribs attribs to check
	 * @return {Boolean} true if the attrib is set
	 */
	AttribsAreSet: _AttribsAreSet,

	/**
	 * 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);
		var oWidget = aArgs[0];
		var oSrcEvent = aArgs[1];

		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>
	 *
	 * @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))
			bResult = fnCompare(Value, Equals, true);

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

		return bResult;
	},

	/**
	 * 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');
	}
}

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

	this.Setup();
}

/**
 * This static method creates an eventlistener class and extends the base
 * class. Creates the required namespace if it doesn't already exist.
 *
 * @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()
	{
	},

	/**
	 * 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: _GetAttribDefault,

	/**
	 * Gets the named attrib.
	 * @param {string} sName attrib to get
	 * @return Value attribute value
	 */
	GetAttrib: _GetAttrib,

	/**
	 * Checks that the named attrib is set
	 * @param {string} sName attrib to check
	 * @return {Boolean} true if the attrib is set
	 */
	AttribIsset: _AttribIsset,

	/**
	 * Checks that the named attribs are set
	 * @param {Array} aAttribs attribs to check
	 * @return {Boolean} true if the attrib is set
	 */
	AttribsAreSet: _AttribsAreSet
};

// end the private scoping
})();
