(function() {
var L = YAHOO.lang, Dom = YAHOO.util.Dom, Evt = YAHOO.util.Event, U = ZC.Util, _GT = U.GetText, YUIPECONTENT = 'yui-pe-content',
	oWidget; // oWidget reused for each new class (helps cut code size once minified)

/**
 * @module Core
 */

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class Checkbox
 */
oWidget = ZC.Core.Widget.Create('Checkbox');
oWidget.prototype.CustomSetupEnd = function()
{
	var sCheckedClass = this.GetAttribDefault('CheckedClass', 'checked'), fnChangeHandler;
	if (!this._elInput || !this.elContainer || !sCheckedClass)
		return false;

	fnChangeHandler = function()
	{
		if (!this._elInput)
			return;

		Dom[this._elInput.checked ? 'addClass' : 'removeClass'](this.elContainer, sCheckedClass);
	}
	Evt.on(this._elInput, 'change', fnChangeHandler, this, true);
	Evt.on(this._elInput, 'click', fnChangeHandler, this, true); // IE doesn't fire change events until blur *sigh*
	fnChangeHandler.call(this);
	return true;
}
oWidget.prototype.GetValue = function()
{
	if (!this._elInput)
		return undefined;

	return this._elInput.checked;
}
oWidget.prototype.HasValue = function()
{
	return true;
}
oWidget.prototype.SetValue = function(bValue)
{
	this._elInput.checked = !!bValue;
	this._FireEventHandlers('change');
}
/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget.Checkbox
 * @class Boolean
 */
ZC.Core.Widget.Boolean = oWidget; // alias

/**
 * Helper methods for the Radio and CheckboxGroup widgets, which both use a series of related INPUT elements.
 * NOTE: this overrides the default _FindElements method, so you need to pass <code>true</code> as the third argument to YAHOO.lang.augment
 * @class GroupedInputHelper
 * @private
 */
var GroupedInputHelper = function() {};

GroupedInputHelper.prototype = {
	_FindElements: function()
	{
		var sID = this.aDef.ID || this._WidgetNameToID();

		if (this.IsReadOnly())
		{
			this._elInput = Dom.get(sID);
			return;
		}

		this.elContainer = Dom.get(sID);
		this._elInput = [];

		if (this.aDef.OptionKeys)
		{
			this._elInput = U.Map(this.aDef.OptionKeys, function(sOptKey)
			{
				return Dom.get(sID + sOptKey);
			});
			this._elInput = U.Filter(this._elInput, function(el) { return L.isObject(el); }); // filter out nulls

			if (this._elInput.length == 0)
				this._elInput = null;
		}

		if (!this.elContainer)
			this._FindContainers();
	},
	/**
	 * Finds the label element for the given input, or all labels for all radios in this widget if not specified.
	 * @param {Array/HTMLElement} [aInputs] optional element(s) to find label for
	 * @return {Array/HTMLElement} either an array of elements or a single label element
	 */
	GetLabelForElement: function(aInputs)
	{
		var aLabels, bReturnArray = true;

		if (L.isUndefined(aInputs))
		{
			aInputs = this._elInput;
		}
		else if (!L.isArray(aInputs))
		{
			bReturnArray = false;
			aInputs = [aInputs];
		}

		aLabels = U.Map(aInputs, function(elRadio)
		{
			var sInputId = elRadio.id,
				aFindLabels = Dom.getElementsBy(function(el) { return (el.htmlFor == sInputId); }, 'label');

			if (aFindLabels.length == 0)
				return undefined;

			return aFindLabels[0];
		}, this);

		return (bReturnArray) ? aLabels : aLabels[0];
	}
};

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class Radio
 * @uses ZC.Core.Widget.GroupedInputHelper
 */
oWidget = ZC.Core.Widget.Create('Radio');
L.augment(oWidget, GroupedInputHelper, true);

/**
 * Finds the selected radio input in the radio group.
 * @private
 * @return {Object} reference to the radio INPUT element
 */
oWidget.prototype._GetSelectedInput = function()
{
	if (!this._elInput)
		return undefined;

	for (var i = 0, iMax = this._elInput.length; i < iMax; ++i)
	{
		if (this._elInput[i].checked)
			return this._elInput[i];
	}
	return undefined;
}

oWidget.prototype.GetValue = function()
{
	if (this.IsReadOnly())
		return this._elInput.value;

	var elSelected = this._GetSelectedInput();
	return elSelected ? elSelected.value : undefined;
}
oWidget.prototype.GetHTMLValue = function()
{
	if (this.IsReadOnly())
		return this._elInput.value;

	var elSelected = this._GetSelectedInput(), elLabel;
	if (!elSelected)
		return undefined;

	elLabel = this.GetLabelForElement(elSelected);
	if (!elLabel)
		return undefined;

	return elLabel.innerHTML;
}
oWidget.prototype.GetTextValue = function()
{
	if (this.IsReadOnly())
		return this._elInput.value;

	var elSelected = this._GetSelectedInput(), elLabel;
	if (!elSelected)
		return undefined;

	elLabel = this.GetLabelForElement(elSelected);
	if (!elLabel)
		return undefined;

	return (elLabel.textContent || elLabel.innerText);
}
oWidget.prototype.SetValue = function(Value)
{
	if (L.isUndefined(Value))
		return this.Clear();

	for (var i = 0, iMax = this._elInput.length; i < iMax; ++i)
	{
		if (this._elInput[i].value == Value)
		{
			this._elInput[i].checked = true;
			this._FireEventHandlers('change');
			return;
		}
	}
}
oWidget.prototype.Clear = function()
{
	if (!L.isUndefined(this.GetValue()))
	{
		Dom.batch(this._elInput, function(el) { el.checked = false; });
		this._FireEventHandlers('change');
	}
}


/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class CheckboxGroup
 * @uses ZC.Core.Widget.GroupedInputHelper
 */
oWidget = ZC.Core.Widget.Create('CheckboxGroup');
L.augment(oWidget, GroupedInputHelper, true);

oWidget.prototype.CustomSetupEnd = function()
{
	var sSelectedClass = this.aDef.SelectedClass || 'checked';
	var fnToggleSelectedClass = function()
	{
		// 'this' is the HTML element the event fires on
		var elAncestor = Dom.getAncestorByTagName(this, 'tr');
		Dom[this.checked ? 'addClass' : 'removeClass'](elAncestor, sSelectedClass);
	}
	Evt.on(this._elInput, 'change', fnToggleSelectedClass);
	if (YAHOO.env.ua.ie)
		Evt.on(this._elInput, 'click', fnToggleSelectedClass);

	/*
	 * Check initial state. The server-side widget adds the class to the tr if
	 * it's set on the server-side, but that doesn't work if the user reloads
	 * the page in their browser.
	 */
	U.ForEach(this._elInput, function(el) { fnToggleSelectedClass.apply(el); });

	return true;
}

/**
 * Finds the selected checkboxes input in the checkbox group.
 * @private
 * @return {Array} array of selected checkbox elements
 */
oWidget.prototype._GetSelectedInputs = function()
{
	return U.Filter(this._elInput, function(el) { return el.checked; });
}
oWidget.prototype.GetValue = function()
{
	var elSelected = this._GetSelectedInputs();
	return U.Map(elSelected, function(el) { return el.value; });
}
oWidget.prototype.GetTextValue = function()
{
	var elSelected = this._GetSelectedInputs(), aTextValues;
	if (!elSelected.length)
		return this.aDef.FormattedValueWhenEmpty || '';

	aTextValues = U.Map(this.GetLabelForElement(elSelected), function (el) { return (el.textContent || el.innerText); });
	return aTextValues.join("\n");
}
oWidget.prototype.GetHTMLValue = function()
{
	var elSelected = this._GetSelectedInputs(), aHTMLValues;
	if (!elSelected.length)
		return this.aDef.FormattedValueWhenEmpty || '';

	aHTMLValues = U.Map(this.GetLabelForElement(elSelected), function (el) { return el.innerHTML; });
	return aHTMLValues.join(this.aDef.HTMLValueSeparator || "<br />");
}
oWidget.prototype.SetValue = function(aValue)
{
	var bChanged = false;
	U.ForEach(this._elInput, function(el)
	{
		if (U.InArray(el.value, aValue))
			if (!el.checked)
			{
				el.checked = true;
				bChanged = true;
			}
		else
			if (el.checked)
			{
				el.checked = false;
				bChanged = true;
			}
	});
	if (bChanged)
		this._FireEventHandlers('change');
}
/**
 * Enable / disable one or more options.
 * @param {mixed} Value Either an option value, or array of option values
 * @param {Boolean} bEnable If true (default), enables this option
 */
oWidget.prototype.EnableOption = function(Value, bEnable)
{
	if (!L.isArray(Value))
		Value = [Value];

	var sAddRemove = bEnable ? 'removeClass' : 'addClass';
	U.ForEach(this._elInput, function(el)
	{
		if (U.InArray(el.value, Value))
		{
			el.disabled = !bEnable;
			var elTR = Dom.getAncestorByTagName(el, 'tr');
			Dom[sAddRemove](elTR, 'disabled');
		}
	});
}

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class Select
 */

oWidget = ZC.Core.Widget.Create('Select');
// used in LinkedSelect as well (TODO: rewrite LinkedSelect to use Select widgets)
var fnSetupCopyOptionClass = function()
{
	var elSelectedOption, sCurrentOptionClass, fnChangeHandler;

	fnChangeHandler = function(elSelect)
	{
		if (elSelect.target)
			elSelect = Evt.getTarget(elSelect);

		if (this.sOldOptionClass)
			Dom.removeClass(elSelect, this.sOldOptionClass);

		var elSelectedOption = elSelect.options[elSelect.selectedIndex],
			sNewClass = Dom.getAttribute(elSelectedOption, 'class');

		if (sNewClass)
		{
			Dom.addClass(elSelect, sNewClass);
			this.sOldOptionClass = sNewClass;
		}
	}

	if (this.aDef.CopyOptionClass)
	{
		Evt.on(this._elInput, 'change', fnChangeHandler, this, true);
		Evt.on(this._elInput, 'keydown', function() { var self = this; this.iKeyInterval = window.setInterval(function() { fnChangeHandler.apply(self, self._elInput); }, 10) }, this, true);
		Evt.on(this._elInput, 'keyup', function() { window.clearInterval(this.iKeyInterval); }, this, true);
		Dom.batch(this._elInput, fnChangeHandler, this, true);
	}

	return true;
}
oWidget.prototype.CustomSetupEnd = fnSetupCopyOptionClass;

oWidget.prototype.GetValue = function(bTextValue)
{
	// lazy binding of the required GetValue implementation
	if (!this._elInput || (this._elInput.type == 'hidden' && this._elInput.tagName.toLowerCase() == 'input'))
	{
		// hidden field (only one option), use parent method
		this.bHiddenField = true;
		this.GetValue = oWidget.superclass.GetValue;
	}
	else if (this._elInput.type == 'select-one')
	{
		this.GetValue = function(bTextValue)
		{
			if (!this._elInput)
				return undefined;

			var iIndex = this._elInput.selectedIndex;
			if (iIndex < 0)
				return undefined;

			var oOpt = this._elInput.options[iIndex];
			return bTextValue ? oOpt.text : oOpt.value;
		}
	}
	else
	{
		this.GetValue = function(bTextValue)
		{
			if (!this._elInput)
				return undefined;

			var aValues = [];

			U.ForEach(this._elInput.options, function(oOpt)
		   	{
				if (oOpt.selected) aValues.push(bTextValue ? oOpt.text : oOpt.value);
			});
			return aValues;
		}
	}

	return this.GetValue(bTextValue);
}
oWidget.prototype.GetTextValue = function()
{
	return this._GetFormattedValue("\n");
}
oWidget.prototype.GetHTMLValue = function()
{
	return this._GetFormattedValue(this.aDef.HTMLValueSeparator || '<br />');
}
oWidget.prototype._GetFormattedValue = function(sSeparator)
{
	if (this.bHiddenField)
		return (this.elContainer.textContent || this.elContainer.innerText);

	var Value = this.GetValue(true);

	if (Value.length == 0)
		return (this.aDef.FormattedValueWhenEmpty || '');

	if (!L.isArray(Value))
		return Value;

	return Value.join(sSeparator);
}
oWidget.prototype.SetValue = function(Value)
{
	var bSingle = !L.isArray(Value),
	 	bChanged = false;

	if (this._elInput.type == 'hidden' && this._elInput.tagName.toLowerCase() == 'input')
	{
		return oWidget.superclass.SetValue(Value);
	}

	for (var iOpt = 0, iOptMax = this._elInput.length; iOpt < iOptMax; ++iOpt)
	{
		var oOpt = this._elInput.options[iOpt];
		if (bSingle)
		{
			if (oOpt.value == Value)
			{
				if (!oOpt.selected)
				{
					oOpt.selected = true;
					this._FireEventHandlers('change');
				}
				return;
			}
		}
		else
		{
			var bInArray = U.InArray(oOpt.value, Value);
			bChanged = bChanged || (oOpt.selected != bInArray);
			oOpt.selected = bInArray;
		}
	}
	if (bChanged)
		this._FireEventHandlers('change');
}
/**
 * Handles changes to the "Options" attrib, replacing the existing options.
 * Attempts to keep the current selection (if the previously-selected option
 * keys still exist).
 *
 * @param {Object} AttribValue a list of options, key => caption
 */
oWidget.prototype.AttribMethod_Options = function(AttribValue)
{
	var aSelectedOptions = (this._elInput.type == 'select-one') ? [this.GetValue()] : this.GetValue();
	this._elInput.options.length = 0;

	for (var sOptVal in AttribValue)
	{
		if (L.hasOwnProperty(AttribValue, sOptVal))
		{
			var elOption = document.createElement('option');
			elOption.value = String(sOptVal);
			elOption.innerHTML = String(AttribValue[sOptVal]);
			elOption.selected = U.InArray(sOptVal, aSelectedOptions);

			this._elInput.appendChild(elOption);
		}
	}
}

oWidget.prototype.Clear = function()
{
	if (this._elInput && this._elInput.options)
	{
		var sFirstOptVal = this._elInput.options[0].value;
		if (sFirstOptVal.match(/^(_?_?Any_?_?|)$/i))
			this._elInput.selectedIndex = 0;
	}
}

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget.Select
 * @class Reference
 */
ZC.Core.Widget.Create('Reference', 'Core', oWidget);

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class Text
 */
oWidget = ZC.Core.Widget.Create('Text');
oWidget.prototype.InsertText = function(sText)
{
	U.InsertAtCursor(this._elInput, sText);
}

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class TextArea
 */
oWidget = ZC.Core.Widget.Create('TextArea');
oWidget.prototype.CustomSetupEnd = function()
{
	if (this.aDef.Size)
	{
		this.AddEvent(this.UpdateNRemaining, ['keyup', 'focus'],  this);
		this.AddEvent(this.HideNRemaining, 'blur',  this);
	}

	return true;
}
oWidget.prototype.UpdateNRemaining = function(event)
{
	if (L.isUndefined(this._elRemaining))
	{
		this._elRemaining = document.createElement('span');
		this._elRemaining.className = 'textarea-remainingchars';
		Dom.insertAfter(this._elRemaining, this._elInput);
	}
	var iUsed = this.GetValue().length, iMax = this.aDef.Size, sRemainingText = '';
	var iRemaining = iMax - iUsed;

	if(iRemaining >= 0)
		sRemainingText = U.sprintf(_GT('Used %1$d of %2$d characters, %3$d remaining.'), iUsed, iMax, iRemaining);
	else
		sRemainingText = U.sprintf(_GT('Used %1$d of %2$d characters.'), iUsed, iMax);

	this._elRemaining.innerHTML = sRemainingText;
	var bOverLimit = iUsed > iMax;
	var sNewClass = bOverLimit ? 'invalid' : 'valid';
	var sOldClass = bOverLimit ? 'valid' : 'invalid';
	Dom.replaceClass(this._elRemaining, sOldClass, sNewClass);

	Dom.removeClass(this._elRemaining, 'invisible');
}
oWidget.prototype.HideNRemaining = function(event)
{
	if (this._elRemaining)
		Dom.addClass(this._elRemaining, 'invisible');
}

oWidget.prototype.Validate = function()
{
	// Enforce the max length
	var iUsed = this.GetValue().length, iMax = this.aDef.Size;
	var sValMsg = U.sprintf(_GT('Please reduce this to %d characters, it is currently at %d.'), iMax, iUsed);

	if (iUsed > iMax)
	{
		this.SetValid(false, sValMsg);
		return false;
	}
	return oWidget.superclass.Validate.call(this);
}

oWidget.prototype.InsertText = function(sText)
{
	U.InsertAtCursor(this._elInput, sText);
}

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class HTML
 */
oWidget = ZC.Core.Widget.Create('HTML');
oWidget.prototype.CustomSetupEnd = function()
{
	var iAttempts = 0,
		fnInitMCE = function()
		{
			var oTinyMCE = this.GetTinyMCE(), oWidget = this, fnOldHandleEvent;
			if (!oTinyMCE)
			{
				if (++iAttempts < 10)
				{
					window.setTimeout(function() { fnInitMCE.call(oWidget); }, 200);
				}
				return;
			}
			fnOldHandleEvent = oTinyMCE.settings.handle_event_callback;
			oTinyMCE.settings.handle_event_callback = function(e)
			{
				if (e.type == 'blur')
					oWidget.oSelectionBookmark = oTinyMCE.selection.getBookmark();

				if (fnOldHandleEvent != '' && !L.isUndefined(fnOldHandleEvent))
					return tinyMCE.evalFunc(typeof(fnOldHandleEvent) == "function" ? fnOldHandleEvent : eval(fnOldHandleEvent), 0, arguments);

				return true;
			}
		};

	if (!this.IsReadOnly())
		Evt.on(window, 'load', fnInitMCE, this, true);

	return true;
}

oWidget.prototype.GetTinyMCE = function()
{
	if (this._elInput && !this.oTinyMCE)
		this.oTinyMCE = tinyMCE.getInstanceById(this._elInput.id);
	return this.oTinyMCE;
}

oWidget.prototype.GetValue = function()
{
	if (this.GetTinyMCE())
		return this.oTinyMCE.getBody().innerHTML; // later tinyMCE versions have getContent method which runs the cleanup methods on it first
	else
		return oWidget.superclass.GetValue.apply(this, arguments);
}
oWidget.prototype.SetValue = function(sValue)
{
	if (this.GetTinyMCE())
		return this.oTinyMCE.getBody().innerHTML = sValue;  // later tinyMCE versions have setContent method which runs the cleanup methods on it first
	else
		return oWidget.superclass.SetValue.apply(this, arguments);
}
oWidget.prototype.InsertText = function(sText)
{
	// convert common entities, and nl2br
	sText = sText.replace(/['"&<>\n]/g, function(sVal) 
	{
		switch (sVal)
		{
			case "\n": return '<br>';
			case "\"": return '&quot;'; 
			case "'":  return '&apos;'; 
			case "&":  return '&amp;';
			case "<":  return '&lt;';
			case ">":  return '&gt;';
		}
	});
	this.InsertHTML(sText);
}
oWidget.prototype.InsertHTML = function(sText)
{
	if (this.GetTinyMCE())
	{
		if (this.oSelectionBookmark)
		{
			this.oTinyMCE.selection.moveToBookmark(this.oSelectionBookmark);
		}
		this.oTinyMCE.execCommand('mceInsertContent', false, sText);
	}
	else
	{
		U.InsertAtCursor(this._elInput, sText);
	}
}

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class Group
 */
oWidget = ZC.Core.Widget.Create('Group');
oWidget.prototype.CustomSetupEnd = function()
{
	if (this.aDef.HideFields)
	{
		var sID, elPlaceholder, fnToggleVisible, elOpenCloseLink, sLabelShow, sLabelHide, sShowLinkTitle, sHideLinkTitle;

		sLabelShow = this.GetAttribDefault('LabelShow', _GT('Show'));
		sLabelHide = this.GetAttribDefault('LabelHide', _GT('Hide'));
		sShowLinkTooltip = this.GetAttribDefault('ShowLinkTooltip', _GT('Show this section'));
		sHideLinkTooltip = this.GetAttribDefault('HideLinkTooltip', _GT('Hide this section'));

		sID = this.aDef.ID;
		elPlaceholder = Dom.get(sID + '-toggle');
		this.elContainer = Dom.get(sID + '-hidediv');

		if (!elPlaceholder)
			return true;

		elOpenCloseLink = this.elOpenCloseLink = document.createElement('a');
		elOpenCloseLink.href = '#';
		elOpenCloseLink.innerHTML = sLabelShow;
		this.oOpenCloseTooltip = new YAHOO.widget.Tooltip('grp-oc-tt-' + this.sName, {
			context: elOpenCloseLink, autofillheight: false, autodismissdelay: 120000,
			effect:  { effect: YAHOO.widget.ContainerEffect.FADE, duration: 0.25 },
			text: sShowLinkTooltip
		});
		elPlaceholder.appendChild(elOpenCloseLink);

		var fnToggleVisible = function(event)
		{
			if (event)
				Evt.stopEvent(event);

			if (this.IsVisible())
			{
				this.Hide();
				this.elOpenCloseLink.innerHTML = sLabelShow;
				this.oOpenCloseTooltip.cfg.setProperty('text', sShowLinkTooltip);
			}
			else
			{
				this.Show();
				this.elOpenCloseLink.innerHTML = sLabelHide;
				this.oOpenCloseTooltip.cfg.setProperty('text', sHideLinkTooltip);
			}
		}

		Evt.on(elOpenCloseLink, 'click', fnToggleVisible, this, true);
		if (this.IsVisible())
			fnToggleVisible.call(this);
	}

	return true;
}

oWidget.prototype.GetTextValue = function()
{
	var aResult = U.Map(this.aChildWidgets, function(oWidget) { return oWidget.GetTextValue(); });
	return aResult.join(this.aDef.TextValueSeparator || ", ");
}

oWidget.prototype.GetHTMLValue = function()
{
	var aResult = U.Map(this.aChildWidgets, function(oWidget) { return oWidget.GetHTMLValue(); });
	return aResult.join(this.aDef.HTMLValueSeparator || "<br />");
}

oWidget.prototype.GetValue = function()
{
	var oValue = {};
	U.ForEach(this.aChildWidgets, function(oWidget, sWidgetName) { oValue[sWidgetName] = oWidget.GetValue(); });
	return oValue;
}

oWidget.prototype.Enable = function(bEnable, sEnableClass, sDisableClass)
{
	U.ForEach(this.aChildWidgets, function(oChild) { oChild.Enable(bEnable, sEnableClass, sDisableClass); });
}
/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget.Group
 * @class Document
 */
ZC.Core.Widget.Create('Document', 'Core', oWidget);
/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget.Group
 * @class Group_List
 */
ZC.Core.Widget.Create('Group_List', 'Core', oWidget);


/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class Group_Matrix
 */
oWidget = ZC.Core.Widget.Create('Group_Matrix', 'Core', ZC.Core.Widget.Group);
oWidget.prototype.CustomSetupEnd = function()
{
	oWidget.superclass.CustomSetupEnd.call(this);

	this.elHeading = Dom.getFirstChildBy(this._elInput, function(el) { return el.tagName.toLowerCase() == 'thead'; });
	this.elBody = Dom.getFirstChildBy(this._elInput, function(el) { return el.tagName.toLowerCase() == 'tbody'; });

	var fnOnSubmit = function()
	{
		var aRows = Dom.getChildrenBy(this.elBody, function(el) { return el.tagName.toLowerCase() == 'tr'; }),
			aRowIDs = U.Map(aRows, function (elRow) { return Dom.getFirstChild(elRow).id.replace(this._elInput.id + '__', ''); }, this);

		this.oForm.AddHiddenField(this.sName + '_rowvalues', aRowIDs);
	}
	this.oForm.AddEvent(fnOnSubmit, 'submit', this);

	return true;
}

/**
 * Adds a row to the table matrix
 * @param {String} sRowID the id for the row
 * @param {String} sCaption the caption HTML for the row
 * @param {Array} aCells array of HTML for each cell
 * @param {Object} oConfig the config array for the new group widget
 */
oWidget.prototype.AddRow = function(sRowID, sCaption, aCells, oConfig)
{
	var elRow = document.createElement('tr'),
		elCaption = document.createElement('th'),
		sCaptionID = this._elInput.id + '__' + sRowID;

	// check it doesn't exist already
	if (Dom.get(sCaptionID))
		return;

	elCaption.scope = 'row';
	elCaption.id = sCaptionID;
	elCaption.innerHTML = sCaption;

	elRow.appendChild(elCaption);

	U.ForEach(aCells, function(sCellHTML)
	{
		var elCell = document.createElement('td');
		elCell.innerHTML = sCellHTML;
		elRow.appendChild(elCell);
	});

	this.elBody.appendChild(elRow);
	Dom.removeClass(this.elHeading, 'hide');

	if (oConfig)
		this.AddChildWidget('gmrow_' + sRowID, oConfig);
}

/**
 * Removes a row from the table matrix
 * @param {String} sRowID the id of the row to remove
 * @return boolean false if the row was not found
 */
oWidget.prototype.RemoveRow = function(sRowID)
{
	var elTH, elRow, sPrefix = this._elInput.id + '__',
		oAnim, fnFinishAnim;

	if (sRowID.indexOf(sPrefix) == -1)
		sRowID = sPrefix + sRowID;

	elTH = Dom.get(sRowID);
	if (elTH)
	{
		elRow = elTH.parentNode;
		fnFinishAnim = function()
		{
			elRow.parentNode.removeChild(elRow);
			if (!Dom.getFirstChildBy(this.elBody, function(el) { return el.tagName.toLowerCase() == 'tr'; }))
			{
				Dom.addClass(this.elHeading, 'hide');
			}
		}
		if (YAHOO.util.Anim)
		{
			oAnim = new YAHOO.util.Anim(elRow, { opacity: { from: 100, to: 0 } }, 0.5);
			oAnim.onComplete.subscribe(fnFinishAnim, this, true);
			oAnim.animate();
		}
		else
		{
			fnFinishAnim.call(this);
		}
	}

	this.RemoveChildWidget('gmrow_' + sRowID.replace(sPrefix, ''));
}

/**
 * Finds the ID of the row containing the given element.
 * @param {HTMLElement} elFind the element to find
 * @return {string} the id of the row containing that element, or undefined if none was found.
 */
oWidget.prototype.FindRowIDWithElement = function(elFind)
{
	var elRow, sRowID,
		sPrefix = this._elInput.id + '__';

	while (Dom.isAncestor(this._elInput, elFind))
	{
		elRow = Dom.getAncestorByTagName(elFind, 'tr');
		sRowID = Dom.getFirstChild(elRow).id;

		if (sRowID.indexOf(sPrefix) < 0)
		{
			// try the parents of the tr we found, in case we have nested tables in our matrix
			elFind = elRow.parentNode;
		}
		else
		{
			return sRowID.replace(sPrefix, '');
		}
	}
	
	return undefined;
}

/**
 * Gets the caption of the row with the given ID.
 * @param {string} the id of a row
 * @return {string} the caption of that row
 */
oWidget.prototype.GetRowCaption = function(sRowID)
{
	var elTH = Dom.get(this._elInput.id + '__' + sRowID);
	if (elTH)
		return (elTH.textContent || elTH.innerText);
	else
		return sRowID;
}

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class TextInsertion
 */
oWidget = ZC.Core.Widget.Create('TextInsertion', 'Core', oWidget);
oWidget.prototype.CustomSetupEnd = function()
{
	ZC.JSManager.GetEvent('ManagerInit').subscribe(function()
	{
		var sDestination, fnInsertClick;

		sDestination = this.GetAttrib('Destination');
		// first try this widget's parent, then this widget's form, then finally search the whole page
		this.oDestWidget = this.oParent.GetWidget(sDestination)
						|| ((this.oParent != this.oForm) && this.oForm.GetWidget(sDestination))
						|| ZC.JSManager.GetWidget(sDestination);

		if (!this.oDestWidget)
			return;

		var fnInsertClick = function()
		{
			this.oDestWidget.InsertText(decodeURIComponent(this.GetWidget('SelectTag').GetValue()));
		}

		this.GetWidget('Insert').AddEvent(fnInsertClick, 'click', this);
		this.GetWidget('SelectTag').AddEvent(fnInsertClick, 'dblclick', this);
	}, this, true);

	return true;
}

/**
 * Class for the widgets that want to create YUI buttons. 
 * Allows widgets to specify the button config in a consistent manner.
 *
 * The following attribs are recognised:
 *
 * <dl>
 * <dd> Type			</dd><dt> Overrides the button type from the default (see YUI docs for the different button types) </dt>
 * <dd> IconImage		</dd><dt> URL for the icon image file. </dt>
 * <dd> IconWidth		</dd><dt> Width of the icon in pixels. </dt>
 * <dd> IconNoCaption	</dd><dt> If true, then the button will be displayed with an icon only. </dt>
 * <dd> IconPosition	</dd><dt> Either "left" (default) or "right". </dt>
 * </dl>
 *
 * @class YUIButtonWrapper
 * @constructor
 * @param {HTMLElement/String} elButton the element (or element id) to replace with a YUI button
 * @param {Array/Boolean} aButtonDef the definition for the button (or just boolean true for defaults)
 * @param {Object} oWidget the widget representing this button (the wrapper will fire click event handlers on this widget when the YUI button is clicked)
 */
ZC.Util.YUIButtonWrapper = function(elSrc, aButtonDef, oWidget)
{
	var oYUIButton, elButton, oButtonConfig = {}, fnOnClick = function() {};

	if (aButtonDef.Type)
	{
		oButtonConfig.type = aButtonDef.Type;
	}

	Dom.removeClass(elSrc, YUIPECONTENT);

	if (elSrc.onclick)
	{
		fnOnClick = elSrc.onclick;
	}

	this.oWidget = oWidget;
	this.oYUIButton = oYUIButton = new YAHOO.widget.Button(elSrc, oButtonConfig);

	oYUIButton.zcWidget = oWidget;

	if (elSrc.className)
	{
		oYUIButton.addClass(elSrc.className);
	}

	if (oWidget)
	{
		oYUIButton.on('click', function(oEvent) { 
			// TODO: widget <-> YUI button event hookup needs fixing. this is a hack to avoid recursive click events...
			if (this._bFiringClickEvent)
				return true;

			if (this.oForm)
				this.oForm.SetSelectedEndWidget(this);

			this._bFiringClickEvent = true;
			this._FireEventHandlers('click', oEvent); 
			fnOnClick(oEvent); 
			this._bFiringClickEvent = false;

			return true;
		}, oWidget, true);
	}

	if (L.isObject(aButtonDef))
	{
		if (aButtonDef.IconImage && aButtonDef.IconWidth)
		{
			elButton = (oYUIButton.getElementsByClassName('first-child'))[0].firstChild;
			Dom.setStyle(elButton, 'background-image', 'url(' + aButtonDef.IconImage + ')');
			Dom.setStyle(elButton, 'background-repeat', 'no-repeat');

			iPadding = Number(aButtonDef.IconWidth);
			if (aButtonDef.IconNoCaption)
			{
				oYUIButton.set('label', '');
				Dom.setStyle(elButton, 'padding-left', iPadding + 'px');
				Dom.setStyle(elButton, 'padding-right', '0px');
				Dom.setStyle(elButton, 'background-position', '50% 50%');
			}
			else
			{
				iPadding += 10;
				if (aButtonDef.IconPosition == 'right')
				{
					Evt.onContentReady(Dom.generateId(elButton), function() {
						var oRegion = Dom.getRegion(elButton);			
						Dom.setStyle(elButton, 'padding-right', iPadding + 'px');
						Dom.setStyle(elButton, 'background-position', (oRegion.width - 7) + 'px 50%');
					});
				}
				else
				{
					Dom.setStyle(elButton, 'padding-left', iPadding + 'px');
					Dom.setStyle(elButton, 'background-position', '7px 50%');
				}
			}
		}
	}
}
ZC.Util.YUIButtonWrapper.prototype = {
	/**
	 * Frees up all the references in this object. Called on page unload to avoid memory leaks in IE.
	 */
	Destruct: function()
	{
		this.oYUIButton = null;
	}
}

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class Submit
 */
oWidget = ZC.Core.Widget.Create('Submit');
oWidget.prototype.CustomSetupEnd = function()
{
	var oLoadingPanel, sLoadingMessage, aButtonDef, sConfirmMessage;

	if (!this._elInput)
		return false;

	if (aButtonDef = this.GetAttribDefault('YUIButtonDef')) // assignment
	{
		this.oButtonWrapper = new U.YUIButtonWrapper(this._elInput, aButtonDef, this);
	}

	if (sLoadingMessage = this.GetAttribDefault('LoadingMessage')) // assignment
	{
		// based on a YUI example - displays a modal "loading" dialog when the submit button is clicked
		oLoadingPanel = new YAHOO.widget.Panel(Dom.generateId(this._elInput) + "wait", {
				fixedcenter:true,
				close:false,
				draggable:false,
				zindex:40000,
				modal:true,
				visible:false
			}
		);

		oLoadingPanel.setHeader(sLoadingMessage);
		oLoadingPanel.setBody('<img style="margin: 0 auto; display: block;" width="220" height="19" src="/zc/images/ajax-loader-bar.gif">');
		oLoadingPanel.render(document.body);

		Evt.on(this._elInput, 'click', function() { oLoadingPanel.show(); });
	}

	if (sConfirmMessage = this.GetAttribDefault('Confirm'))
	{
		var fnSubmitHandler = function(oEvent) {
			if (!confirm(sConfirmMessage)) 
				Evt.stopEvent(oEvent); 
		};

		if (this.oButtonWrapper)
		{
			Evt.on(this.oButtonWrapper.oYUIButton.getForm(), 'submit', fnSubmitHandler);
		}
		else
		{
			Evt.on(this._elInput.form, 'submit', fnSubmitHandler);
		}
	}

	return true;
}

oWidget.prototype.Destruct = function()
{
	oWidget.superclass.Destruct.apply(this, arguments);

	if (this.oButtonWrapper)
	{
		this.oButtonWrapper.Destruct();
		this.oButtonWrapper = null;
	}
}

oWidget.prototype.AttribMethod_value = function(AttribValue)
{
	this._elInput.value = AttribValue;
	if (this.oButtonWrapper)
		this.oButtonWrapper.oYUIButton.set('label', AttribValue);
}

oWidget.prototype.Enable = function(bEnable, sEnableClass, sDisableClass)
{
	oWidget.superclass.Enable.apply(this, arguments);
	if (L.isUndefined(bEnable))
		bEnable = true;

	if (this.oButtonWrapper)
		this.oButtonWrapper.oYUIButton.set('disabled', !bEnable);
}

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class Button
 */
oWidget = ZC.Core.Widget.Create('Button');
oWidget.prototype.CustomSetupEnd = function()
{
	var aButtonDef;

	if (!this._elInput)
		return false;

	if (aButtonDef = this.GetAttribDefault('YUIButtonDef')) // assignment
	{
		this.oButtonWrapper = new U.YUIButtonWrapper(this._elInput, aButtonDef, this);
	}

	return true;
}

oWidget.prototype.Destruct = function()
{
	oWidget.superclass.Destruct.apply(this, arguments);

	if (this.oButtonWrapper)
	{
		this.oButtonWrapper.Destruct();
		this.oButtonWrapper = null;
	}
}

oWidget.prototype.Enable = function(bEnable)
{
	oWidget.superclass.Enable.call(this, bEnable);
	if (this.oButtonWrapper)
		this.oButtonWrapper.oYUIButton.set('disabled', !bEnable);
}
/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget.Button
 * @class AutoPopulate
 */
ZC.Core.Widget.Create('AutoPopulate', 'Core', oWidget);

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class Button_Clear
 */
oWidget = ZC.Core.Widget.Create('Button_Clear');
oWidget.prototype.CustomSetupStart = function()
{
	var elButton, oAttribs, aButtonDef, fnButtonClickHandler, i, iMax;

	if (!this._elInput)
		return false;

	elButton = document.createElement('input');

	oAttribs = this._elInput.attributes;
	// copy attribs from placeholder tag
	for (i = 0, iMax = oAttribs.length; i < iMax; i++)
	{
		Dom.setAttribute(elButton, oAttribs[i].nodeName, oAttribs[i].nodeValue);
	}
	elButton.type = 'button';
	elButton.value = this.aDef.Caption || 'Clear';
	fnButtonClickHandler = function(event)
	{
		if (this.oForm)
			this.oForm.Clear();
		Evt.stopEvent(event);
	}

	Evt.on(elButton, 'click', fnButtonClickHandler, this, true);

	this._elInput.parentNode.replaceChild(elButton, this._elInput);
	this._elInput = elButton;

	if (aButtonDef = this.GetAttribDefault('YUIButtonDef')) // assignment
	{
		this.oButtonWrapper = new U.YUIButtonWrapper(this._elInput, aButtonDef, this);
	}

	return true;
}

oWidget.prototype.Destruct = function()
{
	oWidget.superclass.Destruct.apply(this, arguments);

	if (this.oButtonWrapper)
	{
		this.oButtonWrapper.Destruct();
		this.oButtonWrapper = null;
	}
}

oWidget.prototype.Enable = function(bEnable)
{
	oWidget.superclass.Enable.call(this, bEnable);
	if (this.oButtonWrapper)
		this.oButtonWrapper.oYUIButton.set('disabled', !bEnable);
}

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class Link
 */
oWidget = ZC.Core.Widget.Create('Link');
oWidget.prototype.CustomSetupEnd = function()
{
	var aButtonDef;

	if (!this._elInput)
	{
		YAHOO.log('CustomSetupEnd: no _elInput', 'warn', 'Link ' + this.sName);
		return false;
	}

	if (aButtonDef = this.GetAttribDefault('YUIButtonDef')) // assignment
	{
		this.oButtonWrapper = new U.YUIButtonWrapper(this._elInput, aButtonDef, this);
	}
	else
	{
		YAHOO.log('CustomSetupEnd: no YUIButtonDef', 'warn', 'Link ' + this.sName);
	}
	return true;
}

oWidget.prototype.Destruct = function()
{
	oWidget.superclass.Destruct.apply(this, arguments);

	if (this.oButtonWrapper)
	{
		this.oButtonWrapper.Destruct();
		this.oButtonWrapper = null;
	}
}

oWidget.prototype.Enable = function(bEnable)
{
	oWidget.superclass.Enable.call(this, bEnable);
	if (this.oButtonWrapper)
	{
		var oYUIButton = this.oButtonWrapper.oYUIButton;

		// for some reason YUI doesn't apply the disabled attrib to link
		// buttons, but we can achieve the same result ourselves
		if (bEnable) 
		{
			oYUIButton.removeStateCSSClasses("disabled");
			oYUIButton.set('onclick', null);
		}
		else 
		{
			if (oYUIButton.hasFocus()) 
				oYUIButton.blur();

			oYUIButton.addStateCSSClasses("disabled");

			oYUIButton.removeStateCSSClasses("hover");
			oYUIButton.removeStateCSSClasses("active");
			oYUIButton.removeStateCSSClasses("focus");

			oYUIButton.set('onclick', {
				scope: oYUIButton,
				fn: function(oEvent)
				{
					Evt.stopEvent(oEvent);
					this.blur();
				}
			});
		}
	}
}

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class Links_Button
 */
oWidget = ZC.Core.Widget.Create('Links_Button');
oWidget.prototype.CustomSetupEnd = function()
{
	var aButtonDef, elCaption, elMenu, elButton, sDefaultHREF, fnRenderToBody;

	if (!this._elInput)
		return false;

	aButtonDef = this.GetAttribDefault('YUIButtonDef');
	elCaption = Dom.getFirstChild(this._elInput);

	if (elCaption.tagName.toLowerCase() == 'a')
	{
		sDefaultHREF = elCaption.href;
	}

	elButton = document.createElement('input');
	elButton.type = 'button';
	elButton.value = (elCaption.textContent || elCaption.innerText);
	elButton.className = elCaption.className;
	elButton.id = elCaption.id;
	elCaption.parentNode.replaceChild(elButton, elCaption);

	Dom.removeClass(this._elInput.id + '_menu', YUIPECONTENT);
	this.oYUIButton = new YAHOO.widget.Button(elButton, {
		type: (sDefaultHREF ? 'split' : 'menu'),
		menu: this._elInput.id + '_menu'
	});

	// work-around for z-index issue: render menu to document body element.
	this.oYUIButton.getMenu().subscribe("render",function () {
		document.body.appendChild(this.element);
	});


	// handle default link + LinksDisabled attrib
	// doesn't use the YUI split/menu button disabled attrib as that stops the menu from appearing
	if (sDefaultHREF && !this.GetAttribDefault('LinksDisabled'))
	{
		this.oYUIButton.on('click', function() {
			var sTarget = Dom.getAttribute(this._elInput, 'target'), oNewWin;
			if (sTarget && sTarget != '_self')
			{
				var oNewWin = window.open(sDefaultHREF, sTarget);
				if (oNewWin)
					return;

				// failed to open new window (popup blocker?)
				YAHOO.log("window.open returned null, fallback to current window", "debug", "Links_Button");
			}
			
			window.location = sDefaultHREF; 
		}, this, true);
	}
	else if (this.GetAttribDefault('LinksDisabled'))
	{
		this.oYUIButton.addClass('yui-button-disabled');
		this.oYUIButton.getMenu().cfg.setProperty('disabled', true);
	}
	return true;
}

oWidget.prototype.Destruct = function()
{
	oWidget.superclass.Destruct.apply(this, arguments);

	this.oYUIButton = null;
}

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class MenuButton
 */
oWidget = ZC.Core.Widget.Create('MenuButton');
oWidget.prototype.CustomSetupStart = function()
{
	if (this.GetAttribDefault('Widgets', []).length < 2)
		return false;

	// get child widget IDs
	var elSelect = Dom.get(this.aDef.Widgets.MenuOptions.ID),
		elSubmit = Dom.get(this.aDef.Widgets.MenuSubmit.ID), elNewSubmit,
		fnMenuItemHandler, fnMakeMenuItems,
		sLoadingMessage, oLoadingPanel,
		mMenu, aMenuGroupTitles = []; // will either be a reference to the select widget or an array

	if (!elSelect || !elSubmit)
		return false;

	this._elInput = elSelect;

	var sType;
	if (!this.AttribIsset('Default'))
	{
		sType = "menu";
		for (var i = 0, iMax = elSelect.options.length; i < iMax; i++)
		{
			if (elSelect.options[i].value == '')
			{
				elSelect.removeChild(elSelect.options[i]);
				break;
			}
		}
	}
	else
	{
		sType = "split";
	}

	if (!this.AttribIsset('Options'))
	{
		// simple menu - pass select widget directly
		mMenu = elSelect;
	}
	else
	{
		// change button type to "button" to stop YUI from automatically
		// submitting the form when selecting an item in the menu.
		//
		// note: we have to clone the existing node and change the type before
		// replacing the old node, as IE fails if you just change the type of
		// an existing input.
		elNewSubmit = document.createElement('input');
		elNewSubmit.type = 'button';
		elNewSubmit.id = elSubmit.id;
		elNewSubmit.name = elSubmit.name;
		elNewSubmit.value = elSubmit.value;
		elSubmit.parentNode.replaceChild(elNewSubmit, elSubmit);
		elSubmit = elNewSubmit;

		// menu can't be represented as a simple select widget, generate menu items
		fnMenuItemHandler = function(sType, aArgs, oObj)
		{
			var bResult = true,
				oButton = oObj.Widget.oYUIMenuButton,
				oHiddenField;

			// scope is the selected menu item
			elSelect.value = this.value;

			// create a hidden field to let the server know this button was clicked
			oObj.Widget.oForm.AddHiddenField(oButton.get('name'), oButton.get('value'));

			// fire general widget click events
			oObj.Widget._FireEventHandlers('click', this);

			// individual options can have click events too
			if (oObj.Events)
			{
				U.ForEach(oObj.Events, function (sEvent)
				{
					bResult = bResult && ZC.JSManager.GetEvent(oObj.Widget._InternalEventName(sEvent)).fire(this, oObj.Widget);
				}, this);
			}
			if (oObj.Widget.GetAttribDefault('Submit', true) && bResult && oObj.Submit)
			{

				oButton.submitForm();
			}

			return bResult;
		}
		fnMakeMenuItems = function(aOptions)
		{
			var aMenu = [];

			U.ForEach(aOptions, function(mOption, mKey)
			{
				var aSubMenu,
					aItemDef = { value: mKey, onclick: { fn: fnMenuItemHandler, obj: { Widget: this, Submit: true } } };

				if (L.isObject(mOption))
				{
					aItemDef.onclick.obj.Submit = L.isUndefined(mOption.Submit) || mOption.Submit;
					aItemDef.onclick.obj.Events = !L.isUndefined(mOption.Events) ? mOption.Events : false;

					if (!L.isUndefined(mOption.Group))
					{
						// groups are just arrays of menu items

						aItemDef = fnMakeMenuItems.call(this, mOption.Options);
						if (L.isString(aItemDef.Group))
						{
							aMenuGroupTitles.push(aItemDef.Group);
						}
						else
						{
							aMenuGroupTitles.push(false);
						}
					}
					else
					{
						aItemDef.text = mOption.Text;

						if (!L.isUndefined(mOption.Options))
						{
							if (!L.isUndefined(mOption.Selectable) && !mOption.Selectable)
							{
								delete aItemDef.onclick;
							}

							aSubMenu = fnMakeMenuItems.call(this, mOption.Options);
							if (aSubMenu.length)
								aItemDef.submenu = { id: Dom.generateId(), itemdata: aSubMenu };
						}
					}
				}
				else
				{
					aItemDef.text = mOption;
				}

				aMenu.push(aItemDef);
			}, this);

			return aMenu;
		}

		Dom.addClass(elSelect, 'hide');
		mMenu = fnMakeMenuItems.call(this, this.GetAttrib('Options'));
	}

	this.oYUIMenuButton = new YAHOO.widget.Button(
        elSubmit,
        {
            type: sType,
            menu: mMenu
        }
    );
	U.ForEach(aMenuGroupTitles, function(sTitle, iIndex) { if (sTitle) this.oYUIMenuButton.setItemGroupTitle(sTitle, iIndex); }, this);

	if (this.AttribIsset('Default'))
	{
		this.oYUIMenuButton.subscribe('click', function(oEvent) { 
			this.SetValue(this.GetAttrib('Default'));
			this._FireEventHandlers('click', oEvent); 
		}, this, true);
	}

	if (sLoadingMessage = this.GetAttribDefault('LoadingMessage')) // assignment
	{
		// based on a YUI example - displays a modal "loading" dialog when the submit button is clicked
		oLoadingPanel = new YAHOO.widget.Panel(Dom.generateId(this._elInput) + "wait", {
				fixedcenter:true,
				close:false,
				draggable:false,
				zindex:40000,
				modal:true,
				visible:false
			}
		);

		oLoadingPanel.setHeader(sLoadingMessage);
		oLoadingPanel.setBody('<img style="margin: 0 auto; display: block;" width="220" height="19" src="/zc/images/ajax-loader-bar.gif">');
		oLoadingPanel.render(document.body);

		this.oYUIMenuButton.getMenu().subscribe('click', function() { oLoadingPanel.show(); });
	}

	this.AddEvent(function() { 
		if (this.oForm)
			this.oForm.SetSelectedEndWidget(this); 
	}, 'click', this);

	// don't want to create child widget objects
	this.aDef.Widgets = [];

	return true;
}

oWidget.prototype.Destruct = function()
{
	oWidget.superclass.Destruct.apply(this, arguments);

	this.oYUIMenuButton = null;
}

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class Password
 */
oWidget = ZC.Core.Widget.Create('Password');
oWidget.prototype.GetValue = function()
{
	return this._elInput[0].value;
}
oWidget.prototype.GetTextValue = function()
{
	return U.StrRepeat('*', this.GetValue().length);
}

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget.Password
 * @class Password_Change
 */
oWidget = ZC.Core.Widget.Create('Password_Change', 'Core', oWidget);
oWidget.prototype._FindElements = function()
{
	var sName;

	sName = this._WidgetNameToID();
	this._elInput = [Dom.get(sName), Dom.get(sName + '_confirm')];
	this._FindContainers();
}
oWidget.prototype.Validate = function()
{
	var sVal1, sVal2;

	sVal1 = this._elInput[0].value;
	sVal2 = this._elInput[1].value;

	if (sVal1 && sVal2 && sVal1 != sVal2)
	{
		this.SetValid(false, _GT('The two passwords do not match'));
		return false;
	}
	return oWidget.superclass.Validate.call(this);
}

/**
 * TODO: move the code from linkedselect.js and from the HTML into here (or probably Widget/LinkedSelect.js).
 * This code is currently quite basic in order to make the Clear button work.
 *
 * LinkedSelects should probably end up using Select child widgets on the client-side as well.
 *
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class LinkedSelect
 */

oWidget = ZC.Core.Widget.Create('LinkedSelect');
oWidget.prototype.bChangeRODisplay = false; // bit of a hack for now to stop breakage when one select is RO. will be fixed when new linkedselects are done.
oWidget.prototype.CustomSetupEnd = fnSetupCopyOptionClass;
oWidget.prototype._FindElements = function()
{
	var sBaseName, i, elInput;

	sBaseName = this._WidgetNameToID();
	i = this.GetAttribDefault('FirstVisibleLevel', 0);

	this._elInput = [];
	do
	{
		elInput = Dom.get(sBaseName + '.' + i);
		if (elInput)
			this._elInput.push(elInput);
		i++;
	}
	while (elInput);

	this._FindContainers();
}

oWidget.prototype.Clear = function()
{
	U.ForEach(this._elInput, function(elSelect)
	{
		if (U.InArray(elSelect.options[0].value, ['Any', '__Any__', '']))
			elSelect.selectedIndex = 0;

		if (L.isFunction(elSelect.onchange))
			elSelect.onchange();
	});
}

oWidget.prototype.GetValue = function()
{
	var mVal = this.GetCompoundValue();
	if (L.isUndefined(mVal))
		return undefined;
		
	// The compound value is the value of all the selects to this point concatentated with hyphens
	// GetValue returns the value of the last selected only (the value after the final hyphen)
	// Call GetCompoundValue directly if you also need to know the value of the other selections
	if(mVal.lastIndexOf('-') != -1)
	{
		mVal = mVal.substring(mVal.lastIndexOf('-') + 1);
	}
	return mVal;	
}

oWidget.prototype.GetCompoundValue = function()
{
	var mVal;
	if(L.isUndefined(this._elInput) || L.isUndefined(this._elInput[this._elInput.length -1]))
	{
		return undefined;
	}

	mVal = this._elInput[this._elInput.length -1].value;

	/* 
		The option values in second through to the last select were created by concatenating the previously
		selected values from the earlier selects with hyphens. e.g. third select would contain a value like 123-44-234.
		Default behaviour (GetValue function) is to return the last component only, however if this function
		(GetCompoundValue) is called directly then the whole value from the final select is returned.
	*/
	return mVal;
}

oWidget.prototype.GetTextValue = function()
{
	if(L.isUndefined(this._elInput))
	{
		return undefined;
	}

	var i=0,sText=''; 
	while(i < this._elInput.length)
	{
		if (L.isUndefined(this._elInput[i]))
			return undefined;
			
		sText += this._elInput[i].options[this._elInput[i].selectedIndex].text+' ';
		i++;
	}
	return sText;
}
/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget.LinkedSelect
 * @class LinkedSelect_AllWidgetValues
 */
ZC.Core.Widget.Create('LinkedSelect_AllWidgetValues', 'Core', oWidget);
/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget.LinkedSelect
 * @class LinkedSelect_AllowBlank
 */
ZC.Core.Widget.Create('LinkedSelect_AllowBlank', 'Core', oWidget);
/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget.LinkedSelect
 * @class LinkedSelect_LinkedSearch
 */
ZC.Core.Widget.Create('LinkedSelect_LinkedSearch', 'Core', oWidget);
/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget.LinkedSelect
 * @class LinkedSelect_Multiple
 */
ZC.Core.Widget.Create('LinkedSelect_Multiple', 'Core', oWidget);

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget.LinkedSelect
 * @class SelectRelated
 */
ZC.Core.Widget.Create('SelectRelated', 'Core', oWidget);

/**
 * @namespace ZC.Core.Widget
 * @extends ZC.Core.Widget
 * @class DateTime_FromRange
 */
oWidget = ZC.Core.Widget.Create('DateTime_FromRange');
oWidget.prototype.CustomSetupStart = function()
{
	// convert From/To Date/Time attribs from seconds into Date objects
	U.ForEach(['FromDate','ToDate','FromTime','ToTime'], function(sAttrib)
	{
		if (!this.AttribIsset(sAttrib))
		{
			oWidget.superclass.SetAttrib.call(this, sAttrib, new Date());
		}
		else
		{
			var mValue = this.GetAttrib(sAttrib);
			if (mValue.match(/^\d+$/))
			{
				oWidget.superclass.SetAttrib.call(this, sAttrib, new Date(mValue * 1000));
			}
			else
			{
				oWidget.superclass.SetAttrib.call(this, sAttrib, new Date(mValue));
			}
		}
	}, this);	
	
	return true;
}
oWidget.prototype.GetValue = function()
{
	var oDateWidget = this.aChildWidgets.Date,
		oTimeWidget = this.aChildWidgets.Time,
		// JS cannot parse just a time on its own, add the epoch date for time-only widget.
		aDateValues = ['1970-01-01'], aTimeValues = [''], 
		aValues = [], sValue;

	if (!L.isUndefined(oDateWidget) && !L.isUndefined(oDateWidget.GetValue()))
		aDateValues = oDateWidget.GetValue();
	if (!L.isUndefined(oTimeWidget) && !L.isUndefined(oTimeWidget.GetValue()))
		aTimeValues = oTimeWidget.GetValue();

	U.ForEach(aDateValues, function(sDate)
	{
		U.ForEach(aTimeValues, function(sTime)
		{
			aValues.push(new Date(sDate + ' ' + sTime));
		});
	});

	if (this.GetAttribDefault('Multiple'))
	{
		return aValues;
	}
	else
	{
		return aValues[0];
	}
}

oWidget.prototype.GetTextValue = function()
{
	var mValue = this.GetValue(),
		fnDateFormat = YAHOO.util.Date.format,
		sDisplayFormat = this.GetAttrib('DisplayFormat');

	if (L.isUndefined(mValue))
	{
		return this.GetAttribDefault('DisplayValueWhenNull', '');
	}
	else if (L.isArray(mValue))
	{
		return U.Map(mValue, function(oDate) { return fnDateFormat(oDate, { format: sDisplayFormat }); })
			.join(this.GetAttribDefault('ValueSeparator', ', '));
	}
	else
	{
		return fnDateFormat(this.GetValue, { format: sDisplayFormat });
	}
}

oWidget.prototype.SetValue = function(mValue)
{
	var oDateWidget = this.aChildWidgets.Date,
		oTimeWidget = this.aChildWidgets.Time,
		fnDateFormat = YAHOO.util.Date.format,
		aDateValues = [], aTimeValues = [];

	if (this.GetAttribDefault('Multiple') && L.isArray(mValue))
	{
		if (oDateWidget)
		{
			aDateValues = U.Map(mValue, function(oDate) { fnDateFormat(oDate, "%Y-%m-%d"); });
			oDateWidget.SetValue(aDateValues);
		}
		if (oTimeWidget)
		{
			aTimeValues = U.Map(mValue, function(oDate) { fnDateFormat(oDate, "%H:%M"); });
			oTimeWidget.SetValue(aTimeValues);
		}
	}
	else
	{
		if (!(mValue instanceof Date))
		{
			// maybe its something that can be parsed into a date
			if (L.isNumber(mValue))
			{
				mValue = new Date(mValue * 1000); // seconds -> milliseconds
			}
			else
			{
				mValue = new Date(mValue);
			}
			if (!mValue)
				return;
		}

		if (oDateWidget)
			oDateWidget.SetValue(fnDateFormat(mValue, "%Y-%m-%d"));

		if (oTimeWidget)
			oTimeWidget.SetValue(fnDateFormat(mValue, "%H:%M"));
	}
}

oWidget.prototype.SetAttrib = function(sAttrib, mValue)
{
	var oDateWidget = this.aChildWidgets.Date,
		oTimeWidget = this.aChildWidgets.Time,
		fnDateFormat = YAHOO.util.Date.format,
		oDateOptions = {}, oTimeOptions = {}, mCurrentValue;

	oWidget.superclass.SetAttrib.apply(this, arguments);

	mCurrentValue = this.GetValue();	
	switch (sAttrib)
	{
		case 'DateOptions':
			if (oDateWidget) oDateWidget.SetAttrib('Options', mValue);
			break;
		case 'TimeOptions':
			if (oTimeWidget) oTimeWidget.SetAttrib('Options', mValue);
			break;

		case 'FromDate':
		case 'ToDate':
			if (!oDateWidget) return;

			oFromDate = this.GetAttrib('FromDate');
			oToDate = this.GetAttrib('ToDate');

			for (var oDate = new Date(oFromDate.getTime()); oDate <= oToDate; oDate.setTime(oDate.getTime() + 86400000))
			{
				oDateOptions[fnDateFormat(oDate, {format: "%Y-%m-%d"})] = fnDateFormat(oDate, {format: this.GetAttrib('DateFormat')});
			}

			oDateWidget.SetAttrib('Options', oDateOptions);
			return;

		case 'FromTime':
		case 'ToTime':
			if (!oTimeWidget) return;

			oFromTime = this.GetAttrib('FromTime');
			oToTime = this.GetAttrib('ToTime');

			for (var oDate = new Date(oFromTime.getTime()); oDate <= oToTime; oDate.setTime(oDate.getTime() + 86400000))
			{
				oTimeOptions[fnDateFormat(oDate, {format: "%H:%M"})] = fnDateFormat(oDate, {format: this.GetAttrib('TimeFormat')});
			}

			oTimeWidget.SetAttrib('Options', oTimeOptions);
			break;

		default: return;
	}

	this.SetValue(mCurrentValue);
}
/**
 * @namespace ZC.Core.Widget
 * @class Date_FromRange
 * @extends ZC.Core.Widget.DateTime_FromRange
 */
ZC.Core.Widget.Create('Date_FromRange', 'Core', oWidget);
/**
 * @namespace ZC.Core.Widget
 * @class Time_FromRange
 * @extends ZC.Core.Widget.DateTime_FromRange
 */
ZC.Core.Widget.Create('Time_FromRange', 'Core', oWidget);

})();
