+
+* The default queryDelay value has been changed to 0.2. In low-latency
+implementations (e.g., when queryDelay is set to 0 against a local
+JavaScript DataSource), typeAhead functionality may experience a race condition
+when retrieving the value of the textbox. To avoid this problem, implementers
+are advised not to set the queryDelay value too low.
+
+* Fixed runtime property value validation.
+
+* Implemented new method doBeforeSendQuery().
+
+* Implemented new method destroy().
+
+* Added support for latest JSON lib http://www.json.org/json.js.
+
+* Fixed forceSelection issues with matched selections and multiple selections.
+
+* No longer create var oAnim in global scope.
+
+* The properties alwaysShowContainer and useShadow should not be enabled together.
+
+* There is a known issue in Firefox where the native browser autocomplete
+attribute cannot be disabled programmatically on input boxes that are in use.
+
+
+
+
+
+**** version 2.2.2 ***
+
+* No changes.
+
+
+
+*** version 2.2.1 ***
+
+* Fixed form submission in Safari bug.
+* Fixed broken DS_JSArray support for minQueryLength=0.
+* Improved type checking with YAHOO.lang.
+
+
+
+*** version 2.2.0 ***
+
+* No changes.
+
+
+
+*** version 0.12.2 ***
+
+* No changes.
+
+
+
+*** version 0.12.1 ***
+
+* No longer trigger typeAhead feature when user is backspacing on input text.
+
+
+
+*** version 0.12.0 ***
+
+* The following constants must be defined as static class properties and are no longer
+available as instance properties:
+
+YAHOO.widget.DataSource.ERROR_DATANULL
+YAHOO.widget.DataSource.ERROR_DATAPARSE
+YAHOO.widget.DS_XHR.TYPE_JSON
+YAHOO.widget.DS_XHR.TYPE_XML
+YAHOO.widget.DS_XHR.TYPE_FLAT
+YAHOO.widget.DS_XHR.ERROR_DATAXHR
+
+* The property minQueryLength now supports zero and negative number values for
+DS_JSFunction and DS_XHR objects, to enable null or empty string queries and to disable
+AutoComplete functionality altogether, respectively.
+
+* Enabling the alwaysShowContainer feature will no longer send containerExpandEvent or
+containerCollapseEvent.
+
+
+
+**** version 0.11.3 ***
+
+* The iFrameSrc property has been deprecated. Implementers no longer need to
+specify an https URL to avoid IE security warnings when working with sites over
+SSL.
+
+
+
+*** version 0.11.0 ***
+
+* The method getListIds() has been deprecated for getListItems(), which returns
+an array of DOM references.
+
+* All classnames have been prefixed with "yui-ac-".
+
+* Container elements should no longer have CSS property "display" set to "none".
+
+* The useIFrame property can now be set after instantiation.
+
+* On some browsers, the unmatchedItemSelectEvent may not be fired properly when
+delimiter characters are defined.
+
+* On some browsers, defining delimiter characters while enabling forceSelection
+may result in unexpected behavior.
+
+
+
+*** version 0.10.0 ***
+
+* Initial release
+
+* In order to enable the useIFrame property, it should be set in the
+constructor.
+
+* On some browsers, defining delimiter characters while enabling forceSelection
+may result in unexpected behavior.
diff --git a/lib/yui/autocomplete/assets/autocomplete-core.css b/lib/yui/autocomplete/assets/autocomplete-core.css
index 38a466d85d..9f9c0251ef 100755
--- a/lib/yui/autocomplete/assets/autocomplete-core.css
+++ b/lib/yui/autocomplete/assets/autocomplete-core.css
@@ -2,6 +2,6 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
/* This file intentionally left blank */
diff --git a/lib/yui/autocomplete/assets/skins/sam/autocomplete-skin.css b/lib/yui/autocomplete/assets/skins/sam/autocomplete-skin.css
index 343e4bb96c..0e651d5a64 100755
--- a/lib/yui/autocomplete/assets/skins/sam/autocomplete-skin.css
+++ b/lib/yui/autocomplete/assets/skins/sam/autocomplete-skin.css
@@ -2,7 +2,7 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
/* styles for entire widget */
.yui-skin-sam .yui-ac {
diff --git a/lib/yui/autocomplete/assets/skins/sam/autocomplete.css b/lib/yui/autocomplete/assets/skins/sam/autocomplete.css
index ed09f85f97..40ad715ffb 100755
--- a/lib/yui/autocomplete/assets/skins/sam/autocomplete.css
+++ b/lib/yui/autocomplete/assets/skins/sam/autocomplete.css
@@ -2,6 +2,6 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
.yui-skin-sam .yui-ac{position:relative;font-family:arial;font-size:100%;}.yui-skin-sam .yui-ac-input{position:absolute;width:100%;}.yui-skin-sam .yui-ac-container{position:absolute;top:1.6em;width:100%;}.yui-skin-sam .yui-ac-content{position:absolute;width:100%;border:1px solid #808080;background:#fff;overflow:hidden;z-index:9050;}.yui-skin-sam .yui-ac-shadow{position:absolute;margin:.3em;width:100%;background:#000;-moz-opacity:0.10;opacity:.10;filter:alpha(opacity=10);z-index:9049;}.yui-skin-sam .yui-ac-content ul{margin:0;padding:0;width:100%;}.yui-skin-sam .yui-ac-content li{margin:0;padding:2px 5px;cursor:default;white-space:nowrap;}.yui-skin-sam .yui-ac-content li.yui-ac-prehighlight{background:#B3D4FF;}.yui-skin-sam .yui-ac-content li.yui-ac-highlight{background:#426FD9;color:#FFF;}
diff --git a/lib/yui/autocomplete/autocomplete-debug.js b/lib/yui/autocomplete/autocomplete-debug.js
new file mode 100755
index 0000000000..7689739204
--- /dev/null
+++ b/lib/yui/autocomplete/autocomplete-debug.js
@@ -0,0 +1,3629 @@
+/*
+Copyright (c) 2008, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 2.5.2
+*/
+ /**
+ * The AutoComplete control provides the front-end logic for text-entry suggestion and
+ * completion functionality.
+ *
+ * @module autocomplete
+ * @requires yahoo, dom, event, datasource
+ * @optional animation, connection, get
+ * @namespace YAHOO.widget
+ * @title AutoComplete Widget
+ */
+
+/****************************************************************************/
+/****************************************************************************/
+/****************************************************************************/
+
+/**
+ * The AutoComplete class provides the customizable functionality of a plug-and-play DHTML
+ * auto completion widget. Some key features:
+ *
+ *
Navigate with up/down arrow keys and/or mouse to pick a selection
+ *
The drop down container can "roll down" or "fly out" via configurable
+ * animation
+ *
UI look-and-feel customizable through CSS, including container
+ * attributes, borders, position, fonts, etc
+ *
+ *
+ * @class AutoComplete
+ * @constructor
+ * @param elInput {HTMLElement} DOM element reference of an input field.
+ * @param elInput {String} String ID of an input field.
+ * @param elContainer {HTMLElement} DOM element reference of an existing DIV.
+ * @param elContainer {String} String ID of an existing DIV.
+ * @param oDataSource {YAHOO.widget.DataSource} DataSource instance.
+ * @param oConfigs {Object} (optional) Object literal of configuration params.
+ */
+YAHOO.widget.AutoComplete = function(elInput,elContainer,oDataSource,oConfigs) {
+ if(elInput && elContainer && oDataSource) {
+ // Validate DataSource
+ if(oDataSource instanceof YAHOO.widget.DataSource) {
+ this.dataSource = oDataSource;
+ }
+ else {
+ YAHOO.log("Could not instantiate AutoComplete due to an invalid DataSource", "error", this.toString());
+ return;
+ }
+
+ // Validate input element
+ if(YAHOO.util.Dom.inDocument(elInput)) {
+ if(YAHOO.lang.isString(elInput)) {
+ this._sName = "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput;
+ this._elTextbox = document.getElementById(elInput);
+ }
+ else {
+ this._sName = (elInput.id) ?
+ "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput.id:
+ "instance" + YAHOO.widget.AutoComplete._nIndex;
+ this._elTextbox = elInput;
+ }
+ YAHOO.util.Dom.addClass(this._elTextbox, "yui-ac-input");
+ }
+ else {
+ YAHOO.log("Could not instantiate AutoComplete due to an invalid input element", "error", this.toString());
+ return;
+ }
+
+ // Validate container element
+ if(YAHOO.util.Dom.inDocument(elContainer)) {
+ if(YAHOO.lang.isString(elContainer)) {
+ this._elContainer = document.getElementById(elContainer);
+ }
+ else {
+ this._elContainer = elContainer;
+ }
+ if(this._elContainer.style.display == "none") {
+ YAHOO.log("The container may not display properly if display is set to \"none\" in CSS", "warn", this.toString());
+ }
+
+ // For skinning
+ var elParent = this._elContainer.parentNode;
+ var elTag = elParent.tagName.toLowerCase();
+ if(elTag == "div") {
+ YAHOO.util.Dom.addClass(elParent, "yui-ac");
+ }
+ else {
+ YAHOO.log("Could not find the wrapper element for skinning", "warn", this.toString());
+ }
+ }
+ else {
+ YAHOO.log("Could not instantiate AutoComplete due to an invalid container element", "error", this.toString());
+ return;
+ }
+
+ // Set any config params passed in to override defaults
+ if(oConfigs && (oConfigs.constructor == Object)) {
+ for(var sConfig in oConfigs) {
+ if(sConfig) {
+ this[sConfig] = oConfigs[sConfig];
+ }
+ }
+ }
+
+ // Initialization sequence
+ this._initContainer();
+ this._initProps();
+ this._initList();
+ this._initContainerHelpers();
+
+ // Set up events
+ var oSelf = this;
+ var elTextbox = this._elTextbox;
+ // Events are actually for the content module within the container
+ var elContent = this._elContent;
+
+ // Dom events
+ YAHOO.util.Event.addListener(elTextbox,"keyup",oSelf._onTextboxKeyUp,oSelf);
+ YAHOO.util.Event.addListener(elTextbox,"keydown",oSelf._onTextboxKeyDown,oSelf);
+ YAHOO.util.Event.addListener(elTextbox,"focus",oSelf._onTextboxFocus,oSelf);
+ YAHOO.util.Event.addListener(elTextbox,"blur",oSelf._onTextboxBlur,oSelf);
+ YAHOO.util.Event.addListener(elContent,"mouseover",oSelf._onContainerMouseover,oSelf);
+ YAHOO.util.Event.addListener(elContent,"mouseout",oSelf._onContainerMouseout,oSelf);
+ YAHOO.util.Event.addListener(elContent,"scroll",oSelf._onContainerScroll,oSelf);
+ YAHOO.util.Event.addListener(elContent,"resize",oSelf._onContainerResize,oSelf);
+ YAHOO.util.Event.addListener(elTextbox,"keypress",oSelf._onTextboxKeyPress,oSelf);
+ YAHOO.util.Event.addListener(window,"unload",oSelf._onWindowUnload,oSelf);
+
+ // Custom events
+ this.textboxFocusEvent = new YAHOO.util.CustomEvent("textboxFocus", this);
+ this.textboxKeyEvent = new YAHOO.util.CustomEvent("textboxKey", this);
+ this.dataRequestEvent = new YAHOO.util.CustomEvent("dataRequest", this);
+ this.dataReturnEvent = new YAHOO.util.CustomEvent("dataReturn", this);
+ this.dataErrorEvent = new YAHOO.util.CustomEvent("dataError", this);
+ this.containerExpandEvent = new YAHOO.util.CustomEvent("containerExpand", this);
+ this.typeAheadEvent = new YAHOO.util.CustomEvent("typeAhead", this);
+ this.itemMouseOverEvent = new YAHOO.util.CustomEvent("itemMouseOver", this);
+ this.itemMouseOutEvent = new YAHOO.util.CustomEvent("itemMouseOut", this);
+ this.itemArrowToEvent = new YAHOO.util.CustomEvent("itemArrowTo", this);
+ this.itemArrowFromEvent = new YAHOO.util.CustomEvent("itemArrowFrom", this);
+ this.itemSelectEvent = new YAHOO.util.CustomEvent("itemSelect", this);
+ this.unmatchedItemSelectEvent = new YAHOO.util.CustomEvent("unmatchedItemSelect", this);
+ this.selectionEnforceEvent = new YAHOO.util.CustomEvent("selectionEnforce", this);
+ this.containerCollapseEvent = new YAHOO.util.CustomEvent("containerCollapse", this);
+ this.textboxBlurEvent = new YAHOO.util.CustomEvent("textboxBlur", this);
+
+ // Finish up
+ elTextbox.setAttribute("autocomplete","off");
+ YAHOO.widget.AutoComplete._nIndex++;
+ YAHOO.log("AutoComplete initialized","info",this.toString());
+ }
+ // Required arguments were not found
+ else {
+ YAHOO.log("Could not instantiate AutoComplete due invalid arguments", "error", this.toString());
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The DataSource object that encapsulates the data used for auto completion.
+ * This object should be an inherited object from YAHOO.widget.DataSource.
+ *
+ * @property dataSource
+ * @type YAHOO.widget.DataSource
+ */
+YAHOO.widget.AutoComplete.prototype.dataSource = null;
+
+/**
+ * Number of characters that must be entered before querying for results. A negative value
+ * effectively turns off the widget. A value of 0 allows queries of null or empty string
+ * values.
+ *
+ * @property minQueryLength
+ * @type Number
+ * @default 1
+ */
+YAHOO.widget.AutoComplete.prototype.minQueryLength = 1;
+
+/**
+ * Maximum number of results to display in results container.
+ *
+ * @property maxResultsDisplayed
+ * @type Number
+ * @default 10
+ */
+YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed = 10;
+
+/**
+ * Number of seconds to delay before submitting a query request. If a query
+ * request is received before a previous one has completed its delay, the
+ * previous request is cancelled and the new request is set to the delay.
+ * Implementers should take care when setting this value very low (i.e., less
+ * than 0.2) with low latency DataSources and the typeAhead feature enabled, as
+ * fast typers may see unexpected behavior.
+ *
+ * @property queryDelay
+ * @type Number
+ * @default 0.2
+ */
+YAHOO.widget.AutoComplete.prototype.queryDelay = 0.2;
+
+/**
+ * Class name of a highlighted item within results container.
+ *
+ * @property highlightClassName
+ * @type String
+ * @default "yui-ac-highlight"
+ */
+YAHOO.widget.AutoComplete.prototype.highlightClassName = "yui-ac-highlight";
+
+/**
+ * Class name of a pre-highlighted item within results container.
+ *
+ * @property prehighlightClassName
+ * @type String
+ */
+YAHOO.widget.AutoComplete.prototype.prehighlightClassName = null;
+
+/**
+ * Query delimiter. A single character separator for multiple delimited
+ * selections. Multiple delimiter characteres may be defined as an array of
+ * strings. A null value or empty string indicates that query results cannot
+ * be delimited. This feature is not recommended if you need forceSelection to
+ * be true.
+ *
+ * @property delimChar
+ * @type String | String[]
+ */
+YAHOO.widget.AutoComplete.prototype.delimChar = null;
+
+/**
+ * Whether or not the first item in results container should be automatically highlighted
+ * on expand.
+ *
+ * @property autoHighlight
+ * @type Boolean
+ * @default true
+ */
+YAHOO.widget.AutoComplete.prototype.autoHighlight = true;
+
+/**
+ * Whether or not the input field should be automatically updated
+ * with the first query result as the user types, auto-selecting the substring
+ * that the user has not typed.
+ *
+ * @property typeAhead
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.typeAhead = false;
+
+/**
+ * Whether or not to animate the expansion/collapse of the results container in the
+ * horizontal direction.
+ *
+ * @property animHoriz
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.animHoriz = false;
+
+/**
+ * Whether or not to animate the expansion/collapse of the results container in the
+ * vertical direction.
+ *
+ * @property animVert
+ * @type Boolean
+ * @default true
+ */
+YAHOO.widget.AutoComplete.prototype.animVert = true;
+
+/**
+ * Speed of container expand/collapse animation, in seconds..
+ *
+ * @property animSpeed
+ * @type Number
+ * @default 0.3
+ */
+YAHOO.widget.AutoComplete.prototype.animSpeed = 0.3;
+
+/**
+ * Whether or not to force the user's selection to match one of the query
+ * results. Enabling this feature essentially transforms the input field into a
+ * <select> field. This feature is not recommended with delimiter character(s)
+ * defined.
+ *
+ * @property forceSelection
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.forceSelection = false;
+
+/**
+ * Whether or not to allow browsers to cache user-typed input in the input
+ * field. Disabling this feature will prevent the widget from setting the
+ * autocomplete="off" on the input field. When autocomplete="off"
+ * and users click the back button after form submission, user-typed input can
+ * be prefilled by the browser from its cache. This caching of user input may
+ * not be desired for sensitive data, such as credit card numbers, in which
+ * case, implementers should consider setting allowBrowserAutocomplete to false.
+ *
+ * @property allowBrowserAutocomplete
+ * @type Boolean
+ * @default true
+ */
+YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete = true;
+
+/**
+ * Whether or not the results container should always be displayed.
+ * Enabling this feature displays the container when the widget is instantiated
+ * and prevents the toggling of the container to a collapsed state.
+ *
+ * @property alwaysShowContainer
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.alwaysShowContainer = false;
+
+/**
+ * Whether or not to use an iFrame to layer over Windows form elements in
+ * IE. Set to true only when the results container will be on top of a
+ * <select> field in IE and thus exposed to the IE z-index bug (i.e.,
+ * 5.5 < IE < 7).
+ *
+ * @property useIFrame
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.useIFrame = false;
+
+/**
+ * Whether or not the results container should have a shadow.
+ *
+ * @property useShadow
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.useShadow = false;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Public accessor to the unique name of the AutoComplete instance.
+ *
+ * @method toString
+ * @return {String} Unique name of the AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.toString = function() {
+ return "AutoComplete " + this._sName;
+};
+
+ /**
+ * Returns true if container is in an expanded state, false otherwise.
+ *
+ * @method isContainerOpen
+ * @return {Boolean} Returns true if container is in an expanded state, false otherwise.
+ */
+YAHOO.widget.AutoComplete.prototype.isContainerOpen = function() {
+ return this._bContainerOpen;
+};
+
+/**
+ * Public accessor to the internal array of DOM <li> elements that
+ * display query results within the results container.
+ *
+ * @method getListItems
+ * @return {HTMLElement[]} Array of <li> elements within the results container.
+ */
+YAHOO.widget.AutoComplete.prototype.getListItems = function() {
+ return this._aListItems;
+};
+
+/**
+ * Public accessor to the data held in an <li> element of the
+ * results container.
+ *
+ * @method getListItemData
+ * @return {Object | Object[]} Object or array of result data or null
+ */
+YAHOO.widget.AutoComplete.prototype.getListItemData = function(oListItem) {
+ if(oListItem._oResultData) {
+ return oListItem._oResultData;
+ }
+ else {
+ return false;
+ }
+};
+
+/**
+ * Sets HTML markup for the results container header. This markup will be
+ * inserted within a <div> tag with a class of "yui-ac-hd".
+ *
+ * @method setHeader
+ * @param sHeader {String} HTML markup for results container header.
+ */
+YAHOO.widget.AutoComplete.prototype.setHeader = function(sHeader) {
+ if(this._elHeader) {
+ var elHeader = this._elHeader;
+ if(sHeader) {
+ elHeader.innerHTML = sHeader;
+ elHeader.style.display = "block";
+ }
+ else {
+ elHeader.innerHTML = "";
+ elHeader.style.display = "none";
+ }
+ }
+};
+
+/**
+ * Sets HTML markup for the results container footer. This markup will be
+ * inserted within a <div> tag with a class of "yui-ac-ft".
+ *
+ * @method setFooter
+ * @param sFooter {String} HTML markup for results container footer.
+ */
+YAHOO.widget.AutoComplete.prototype.setFooter = function(sFooter) {
+ if(this._elFooter) {
+ var elFooter = this._elFooter;
+ if(sFooter) {
+ elFooter.innerHTML = sFooter;
+ elFooter.style.display = "block";
+ }
+ else {
+ elFooter.innerHTML = "";
+ elFooter.style.display = "none";
+ }
+ }
+};
+
+/**
+ * Sets HTML markup for the results container body. This markup will be
+ * inserted within a <div> tag with a class of "yui-ac-bd".
+ *
+ * @method setBody
+ * @param sBody {String} HTML markup for results container body.
+ */
+YAHOO.widget.AutoComplete.prototype.setBody = function(sBody) {
+ if(this._elBody) {
+ var elBody = this._elBody;
+ if(sBody) {
+ elBody.innerHTML = sBody;
+ elBody.style.display = "block";
+ elBody.style.display = "block";
+ }
+ else {
+ elBody.innerHTML = "";
+ elBody.style.display = "none";
+ }
+ this._maxResultsDisplayed = 0;
+ }
+};
+
+/**
+ * Overridable method that converts a result item object into HTML markup
+ * for display. Return data values are accessible via the oResultItem object,
+ * and the key return value will always be oResultItem[0]. Markup will be
+ * displayed within <li> element tags in the container.
+ *
+ * @method formatResult
+ * @param oResultItem {Object} Result item representing one query result. Data is held in an array.
+ * @param sQuery {String} The current query string.
+ * @return {String} HTML markup of formatted result data.
+ */
+YAHOO.widget.AutoComplete.prototype.formatResult = function(oResultItem, sQuery) {
+ var sResult = oResultItem[0];
+ if(sResult) {
+ return sResult;
+ }
+ else {
+ return "";
+ }
+};
+
+/**
+ * Overridable method called before container expands allows implementers to access data
+ * and DOM elements.
+ *
+ * @method doBeforeExpandContainer
+ * @param elTextbox {HTMLElement} The text input box.
+ * @param elContainer {HTMLElement} The container element.
+ * @param sQuery {String} The query string.
+ * @param aResults {Object[]} An array of query results.
+ * @return {Boolean} Return true to continue expanding container, false to cancel the expand.
+ */
+YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer = function(elTextbox, elContainer, sQuery, aResults) {
+ return true;
+};
+
+/**
+ * Makes query request to the DataSource.
+ *
+ * @method sendQuery
+ * @param sQuery {String} Query string.
+ */
+YAHOO.widget.AutoComplete.prototype.sendQuery = function(sQuery) {
+ this._sendQuery(sQuery);
+};
+
+/**
+ * Overridable method gives implementers access to the query before it gets sent.
+ *
+ * @method doBeforeSendQuery
+ * @param sQuery {String} Query string.
+ * @return {String} Query string.
+ */
+YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery = function(sQuery) {
+ return sQuery;
+};
+
+/**
+ * Nulls out the entire AutoComplete instance and related objects, removes attached
+ * event listeners, and clears out DOM elements inside the container. After
+ * calling this method, the instance reference should be expliclitly nulled by
+ * implementer, as in myDataTable = null. Use with caution!
+ *
+ * @method destroy
+ */
+YAHOO.widget.AutoComplete.prototype.destroy = function() {
+ var instanceName = this.toString();
+ var elInput = this._elTextbox;
+ var elContainer = this._elContainer;
+
+ // Unhook custom events
+ this.textboxFocusEvent.unsubscribeAll();
+ this.textboxKeyEvent.unsubscribeAll();
+ this.dataRequestEvent.unsubscribeAll();
+ this.dataReturnEvent.unsubscribeAll();
+ this.dataErrorEvent.unsubscribeAll();
+ this.containerExpandEvent.unsubscribeAll();
+ this.typeAheadEvent.unsubscribeAll();
+ this.itemMouseOverEvent.unsubscribeAll();
+ this.itemMouseOutEvent.unsubscribeAll();
+ this.itemArrowToEvent.unsubscribeAll();
+ this.itemArrowFromEvent.unsubscribeAll();
+ this.itemSelectEvent.unsubscribeAll();
+ this.unmatchedItemSelectEvent.unsubscribeAll();
+ this.selectionEnforceEvent.unsubscribeAll();
+ this.containerCollapseEvent.unsubscribeAll();
+ this.textboxBlurEvent.unsubscribeAll();
+
+ // Unhook DOM events
+ YAHOO.util.Event.purgeElement(elInput, true);
+ YAHOO.util.Event.purgeElement(elContainer, true);
+
+ // Remove DOM elements
+ elContainer.innerHTML = "";
+
+ // Null out objects
+ for(var key in this) {
+ if(YAHOO.lang.hasOwnProperty(this, key)) {
+ this[key] = null;
+ }
+ }
+
+ YAHOO.log("AutoComplete instance destroyed: " + instanceName);
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public events
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Fired when the input field receives focus.
+ *
+ * @event textboxFocusEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.textboxFocusEvent = null;
+
+/**
+ * Fired when the input field receives key input.
+ *
+ * @event textboxKeyEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param nKeycode {Number} The keycode number.
+ */
+YAHOO.widget.AutoComplete.prototype.textboxKeyEvent = null;
+
+/**
+ * Fired when the AutoComplete instance makes a query to the DataSource.
+ *
+ * @event dataRequestEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param sQuery {String} The query string.
+ */
+YAHOO.widget.AutoComplete.prototype.dataRequestEvent = null;
+
+/**
+ * Fired when the AutoComplete instance receives query results from the data
+ * source.
+ *
+ * @event dataReturnEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param sQuery {String} The query string.
+ * @param aResults {Object[]} Results array.
+ */
+YAHOO.widget.AutoComplete.prototype.dataReturnEvent = null;
+
+/**
+ * Fired when the AutoComplete instance does not receive query results from the
+ * DataSource due to an error.
+ *
+ * @event dataErrorEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param sQuery {String} The query string.
+ */
+YAHOO.widget.AutoComplete.prototype.dataErrorEvent = null;
+
+/**
+ * Fired when the results container is expanded.
+ *
+ * @event containerExpandEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.containerExpandEvent = null;
+
+/**
+ * Fired when the input field has been prefilled by the type-ahead
+ * feature.
+ *
+ * @event typeAheadEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param sQuery {String} The query string.
+ * @param sPrefill {String} The prefill string.
+ */
+YAHOO.widget.AutoComplete.prototype.typeAheadEvent = null;
+
+/**
+ * Fired when result item has been moused over.
+ *
+ * @event itemMouseOverEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param elItem {HTMLElement} The <li> element item moused to.
+ */
+YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent = null;
+
+/**
+ * Fired when result item has been moused out.
+ *
+ * @event itemMouseOutEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param elItem {HTMLElement} The <li> element item moused from.
+ */
+YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent = null;
+
+/**
+ * Fired when result item has been arrowed to.
+ *
+ * @event itemArrowToEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param elItem {HTMLElement} The <li> element item arrowed to.
+ */
+YAHOO.widget.AutoComplete.prototype.itemArrowToEvent = null;
+
+/**
+ * Fired when result item has been arrowed away from.
+ *
+ * @event itemArrowFromEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param elItem {HTMLElement} The <li> element item arrowed from.
+ */
+YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent = null;
+
+/**
+ * Fired when an item is selected via mouse click, ENTER key, or TAB key.
+ *
+ * @event itemSelectEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param elItem {HTMLElement} The selected <li> element item.
+ * @param oData {Object} The data returned for the item, either as an object,
+ * or mapped from the schema into an array.
+ */
+YAHOO.widget.AutoComplete.prototype.itemSelectEvent = null;
+
+/**
+ * Fired when a user selection does not match any of the displayed result items.
+ *
+ * @event unmatchedItemSelectEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent = null;
+
+/**
+ * Fired if forceSelection is enabled and the user's input has been cleared
+ * because it did not match one of the returned query results.
+ *
+ * @event selectionEnforceEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent = null;
+
+/**
+ * Fired when the results container is collapsed.
+ *
+ * @event containerCollapseEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.containerCollapseEvent = null;
+
+/**
+ * Fired when the input field loses focus.
+ *
+ * @event textboxBlurEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.textboxBlurEvent = null;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Internal class variable to index multiple AutoComplete instances.
+ *
+ * @property _nIndex
+ * @type Number
+ * @default 0
+ * @private
+ */
+YAHOO.widget.AutoComplete._nIndex = 0;
+
+/**
+ * Name of AutoComplete instance.
+ *
+ * @property _sName
+ * @type String
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._sName = null;
+
+/**
+ * Text input field DOM element.
+ *
+ * @property _elTextbox
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elTextbox = null;
+
+/**
+ * Container DOM element.
+ *
+ * @property _elContainer
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elContainer = null;
+
+/**
+ * Reference to content element within container element.
+ *
+ * @property _elContent
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elContent = null;
+
+/**
+ * Reference to header element within content element.
+ *
+ * @property _elHeader
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elHeader = null;
+
+/**
+ * Reference to body element within content element.
+ *
+ * @property _elBody
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elBody = null;
+
+/**
+ * Reference to footer element within content element.
+ *
+ * @property _elFooter
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elFooter = null;
+
+/**
+ * Reference to shadow element within container element.
+ *
+ * @property _elShadow
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elShadow = null;
+
+/**
+ * Reference to iframe element within container element.
+ *
+ * @property _elIFrame
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elIFrame = null;
+
+/**
+ * Whether or not the input field is currently in focus. If query results come back
+ * but the user has already moved on, do not proceed with auto complete behavior.
+ *
+ * @property _bFocused
+ * @type Boolean
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._bFocused = true;
+
+/**
+ * Animation instance for container expand/collapse.
+ *
+ * @property _oAnim
+ * @type Boolean
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._oAnim = null;
+
+/**
+ * Whether or not the results container is currently open.
+ *
+ * @property _bContainerOpen
+ * @type Boolean
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._bContainerOpen = false;
+
+/**
+ * Whether or not the mouse is currently over the results
+ * container. This is necessary in order to prevent clicks on container items
+ * from being text input field blur events.
+ *
+ * @property _bOverContainer
+ * @type Boolean
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._bOverContainer = false;
+
+/**
+ * Array of <li> elements references that contain query results within the
+ * results container.
+ *
+ * @property _aListItems
+ * @type HTMLElement[]
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._aListItems = null;
+
+/**
+ * Number of <li> elements currently displayed in results container.
+ *
+ * @property _nDisplayedItems
+ * @type Number
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._nDisplayedItems = 0;
+
+/**
+ * Internal count of <li> elements displayed and hidden in results container.
+ *
+ * @property _maxResultsDisplayed
+ * @type Number
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._maxResultsDisplayed = 0;
+
+/**
+ * Current query string
+ *
+ * @property _sCurQuery
+ * @type String
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._sCurQuery = null;
+
+/**
+ * Past queries this session (for saving delimited queries).
+ *
+ * @property _sSavedQuery
+ * @type String
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._sSavedQuery = null;
+
+/**
+ * Pointer to the currently highlighted <li> element in the container.
+ *
+ * @property _oCurItem
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._oCurItem = null;
+
+/**
+ * Whether or not an item has been selected since the container was populated
+ * with results. Reset to false by _populateList, and set to true when item is
+ * selected.
+ *
+ * @property _bItemSelected
+ * @type Boolean
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._bItemSelected = false;
+
+/**
+ * Key code of the last key pressed in textbox.
+ *
+ * @property _nKeyCode
+ * @type Number
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._nKeyCode = null;
+
+/**
+ * Delay timeout ID.
+ *
+ * @property _nDelayID
+ * @type Number
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._nDelayID = -1;
+
+/**
+ * Src to iFrame used when useIFrame = true. Supports implementations over SSL
+ * as well.
+ *
+ * @property _iFrameSrc
+ * @type String
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._iFrameSrc = "javascript:false;";
+
+/**
+ * For users typing via certain IMEs, queries must be triggered by intervals,
+ * since key events yet supported across all browsers for all IMEs.
+ *
+ * @property _queryInterval
+ * @type Object
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._queryInterval = null;
+
+/**
+ * Internal tracker to last known textbox value, used to determine whether or not
+ * to trigger a query via interval for certain IME users.
+ *
+ * @event _sLastTextboxValue
+ * @type String
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._sLastTextboxValue = null;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Updates and validates latest public config properties.
+ *
+ * @method __initProps
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._initProps = function() {
+ // Correct any invalid values
+ var minQueryLength = this.minQueryLength;
+ if(!YAHOO.lang.isNumber(minQueryLength)) {
+ this.minQueryLength = 1;
+ }
+ var maxResultsDisplayed = this.maxResultsDisplayed;
+ if(!YAHOO.lang.isNumber(maxResultsDisplayed) || (maxResultsDisplayed < 1)) {
+ this.maxResultsDisplayed = 10;
+ }
+ var queryDelay = this.queryDelay;
+ if(!YAHOO.lang.isNumber(queryDelay) || (queryDelay < 0)) {
+ this.queryDelay = 0.2;
+ }
+ var delimChar = this.delimChar;
+ if(YAHOO.lang.isString(delimChar) && (delimChar.length > 0)) {
+ this.delimChar = [delimChar];
+ }
+ else if(!YAHOO.lang.isArray(delimChar)) {
+ this.delimChar = null;
+ }
+ var animSpeed = this.animSpeed;
+ if((this.animHoriz || this.animVert) && YAHOO.util.Anim) {
+ if(!YAHOO.lang.isNumber(animSpeed) || (animSpeed < 0)) {
+ this.animSpeed = 0.3;
+ }
+ if(!this._oAnim ) {
+ this._oAnim = new YAHOO.util.Anim(this._elContent, {}, this.animSpeed);
+ }
+ else {
+ this._oAnim.duration = this.animSpeed;
+ }
+ }
+ if(this.forceSelection && delimChar) {
+ YAHOO.log("The forceSelection feature has been enabled with delimChar defined.","warn", this.toString());
+ }
+};
+
+/**
+ * Initializes the results container helpers if they are enabled and do
+ * not exist
+ *
+ * @method _initContainerHelpers
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._initContainerHelpers = function() {
+ if(this.useShadow && !this._elShadow) {
+ var elShadow = document.createElement("div");
+ elShadow.className = "yui-ac-shadow";
+ this._elShadow = this._elContainer.appendChild(elShadow);
+ }
+ if(this.useIFrame && !this._elIFrame) {
+ var elIFrame = document.createElement("iframe");
+ elIFrame.src = this._iFrameSrc;
+ elIFrame.frameBorder = 0;
+ elIFrame.scrolling = "no";
+ elIFrame.style.position = "absolute";
+ elIFrame.style.width = "100%";
+ elIFrame.style.height = "100%";
+ elIFrame.tabIndex = -1;
+ this._elIFrame = this._elContainer.appendChild(elIFrame);
+ }
+};
+
+/**
+ * Initializes the results container once at object creation
+ *
+ * @method _initContainer
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._initContainer = function() {
+ YAHOO.util.Dom.addClass(this._elContainer, "yui-ac-container");
+
+ if(!this._elContent) {
+ // The elContent div helps size the iframe and shadow properly
+ var elContent = document.createElement("div");
+ elContent.className = "yui-ac-content";
+ elContent.style.display = "none";
+ this._elContent = this._elContainer.appendChild(elContent);
+
+ var elHeader = document.createElement("div");
+ elHeader.className = "yui-ac-hd";
+ elHeader.style.display = "none";
+ this._elHeader = this._elContent.appendChild(elHeader);
+
+ var elBody = document.createElement("div");
+ elBody.className = "yui-ac-bd";
+ this._elBody = this._elContent.appendChild(elBody);
+
+ var elFooter = document.createElement("div");
+ elFooter.className = "yui-ac-ft";
+ elFooter.style.display = "none";
+ this._elFooter = this._elContent.appendChild(elFooter);
+ }
+ else {
+ YAHOO.log("Could not initialize the container","warn",this.toString());
+ }
+};
+
+/**
+ * Clears out contents of container body and creates up to
+ * YAHOO.widget.AutoComplete#maxResultsDisplayed <li> elements in an
+ * <ul> element.
+ *
+ * @method _initList
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._initList = function() {
+ this._aListItems = [];
+ while(this._elBody.hasChildNodes()) {
+ var oldListItems = this.getListItems();
+ if(oldListItems) {
+ for(var oldi = oldListItems.length-1; oldi >= 0; oldi--) {
+ oldListItems[oldi] = null;
+ }
+ }
+ this._elBody.innerHTML = "";
+ }
+
+ var oList = document.createElement("ul");
+ oList = this._elBody.appendChild(oList);
+ for(var i=0; i= 18 && nKeyCode <= 20) || // alt,pause/break,caps lock
+ (nKeyCode == 27) || // esc
+ (nKeyCode >= 33 && nKeyCode <= 35) || // page up,page down,end
+ /*(nKeyCode >= 36 && nKeyCode <= 38) || // home,left,up
+ (nKeyCode == 40) || // down*/
+ (nKeyCode >= 36 && nKeyCode <= 40) || // home,left,up, right, down
+ (nKeyCode >= 44 && nKeyCode <= 45)) { // print screen,insert
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Makes query request to the DataSource.
+ *
+ * @method _sendQuery
+ * @param sQuery {String} Query string.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._sendQuery = function(sQuery) {
+ // Widget has been effectively turned off
+ if(this.minQueryLength == -1) {
+ this._toggleContainer(false);
+ YAHOO.log("Property minQueryLength is set to -1", "info", this.toString());
+ return;
+ }
+ // Delimiter has been enabled
+ var aDelimChar = (this.delimChar) ? this.delimChar : null;
+ if(aDelimChar) {
+ // Loop through all possible delimiters and find the latest one
+ // A " " may be a false positive if they are defined as delimiters AND
+ // are used to separate delimited queries
+ var nDelimIndex = -1;
+ for(var i = aDelimChar.length-1; i >= 0; i--) {
+ var nNewIndex = sQuery.lastIndexOf(aDelimChar[i]);
+ if(nNewIndex > nDelimIndex) {
+ nDelimIndex = nNewIndex;
+ }
+ }
+ // If we think the last delimiter is a space (" "), make sure it is NOT
+ // a false positive by also checking the char directly before it
+ if(aDelimChar[i] == " ") {
+ for (var j = aDelimChar.length-1; j >= 0; j--) {
+ if(sQuery[nDelimIndex - 1] == aDelimChar[j]) {
+ nDelimIndex--;
+ break;
+ }
+ }
+ }
+ // A delimiter has been found so extract the latest query
+ if(nDelimIndex > -1) {
+ var nQueryStart = nDelimIndex + 1;
+ // Trim any white space from the beginning...
+ while(sQuery.charAt(nQueryStart) == " ") {
+ nQueryStart += 1;
+ }
+ // ...and save the rest of the string for later
+ this._sSavedQuery = sQuery.substring(0,nQueryStart);
+ // Here is the query itself
+ sQuery = sQuery.substr(nQueryStart);
+ }
+ else if(sQuery.indexOf(this._sSavedQuery) < 0){
+ this._sSavedQuery = null;
+ }
+ }
+
+ // Don't search queries that are too short
+ if((sQuery && (sQuery.length < this.minQueryLength)) || (!sQuery && this.minQueryLength > 0)) {
+ if(this._nDelayID != -1) {
+ clearTimeout(this._nDelayID);
+ }
+ this._toggleContainer(false);
+ YAHOO.log("Query \"" + sQuery + "\" is too short", "info", this.toString());
+ return;
+ }
+
+ sQuery = encodeURIComponent(sQuery);
+ this._nDelayID = -1; // Reset timeout ID because request has been made
+ sQuery = this.doBeforeSendQuery(sQuery);
+ this.dataRequestEvent.fire(this, sQuery);
+ YAHOO.log("Sending query \"" + sQuery + "\"", "info", this.toString());
+ this.dataSource.getResults(this._populateList, sQuery, this);
+};
+
+/**
+ * Populates the array of <li> elements in the container with query
+ * results. This method is passed to YAHOO.widget.DataSource#getResults as a
+ * callback function so results from the DataSource instance are returned to the
+ * AutoComplete instance.
+ *
+ * @method _populateList
+ * @param sQuery {String} The query string.
+ * @param aResults {Object[]} An array of query result objects from the DataSource.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._populateList = function(sQuery, aResults, oSelf) {
+ if(aResults === null) {
+ oSelf.dataErrorEvent.fire(oSelf, sQuery);
+ }
+ if(!oSelf._bFocused || !aResults) {
+ YAHOO.log("Could not populate list", "info", oSelf.toString());
+ return;
+ }
+
+ var isOpera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);
+ var contentStyle = oSelf._elContent.style;
+ contentStyle.width = (!isOpera) ? null : "";
+ contentStyle.height = (!isOpera) ? null : "";
+
+ var sCurQuery = decodeURIComponent(sQuery);
+ oSelf._sCurQuery = sCurQuery;
+ oSelf._bItemSelected = false;
+
+ if(oSelf._maxResultsDisplayed != oSelf.maxResultsDisplayed) {
+ oSelf._initList();
+ }
+
+ var nItems = Math.min(aResults.length,oSelf.maxResultsDisplayed);
+ oSelf._nDisplayedItems = nItems;
+ if(nItems > 0) {
+ oSelf._initContainerHelpers();
+ var aItems = oSelf._aListItems;
+
+ // Fill items with data
+ for(var i = nItems-1; i >= 0; i--) {
+ var oItemi = aItems[i];
+ var oResultItemi = aResults[i];
+ oItemi.innerHTML = oSelf.formatResult(oResultItemi, sCurQuery);
+ oItemi.style.display = "list-item";
+ oItemi._sResultKey = oResultItemi[0];
+ oItemi._oResultData = oResultItemi;
+
+ }
+
+ // Empty out remaining items if any
+ for(var j = aItems.length-1; j >= nItems ; j--) {
+ var oItemj = aItems[j];
+ oItemj.innerHTML = null;
+ oItemj.style.display = "none";
+ oItemj._sResultKey = null;
+ oItemj._oResultData = null;
+ }
+
+ // Expand the container
+ var ok = oSelf.doBeforeExpandContainer(oSelf._elTextbox, oSelf._elContainer, sQuery, aResults);
+ oSelf._toggleContainer(ok);
+
+ if(oSelf.autoHighlight) {
+ // Go to the first item
+ var oFirstItem = aItems[0];
+ oSelf._toggleHighlight(oFirstItem,"to");
+ oSelf.itemArrowToEvent.fire(oSelf, oFirstItem);
+ YAHOO.log("Arrowed to first item", "info", oSelf.toString());
+ oSelf._typeAhead(oFirstItem,sQuery);
+ }
+ else {
+ oSelf._oCurItem = null;
+ }
+ }
+ else {
+ oSelf._toggleContainer(false);
+ }
+ oSelf.dataReturnEvent.fire(oSelf, sQuery, aResults);
+ YAHOO.log("Container populated with list items", "info", oSelf.toString());
+
+};
+
+/**
+ * When forceSelection is true and the user attempts
+ * leave the text input box without selecting an item from the query results,
+ * the user selection is cleared.
+ *
+ * @method _clearSelection
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._clearSelection = function() {
+ var sValue = this._elTextbox.value;
+ var sChar = (this.delimChar) ? this.delimChar[0] : null;
+ var nIndex = (sChar) ? sValue.lastIndexOf(sChar, sValue.length-2) : -1;
+ if(nIndex > -1) {
+ this._elTextbox.value = sValue.substring(0,nIndex);
+ }
+ else {
+ this._elTextbox.value = "";
+ }
+ this._sSavedQuery = this._elTextbox.value;
+
+ // Fire custom event
+ this.selectionEnforceEvent.fire(this);
+ YAHOO.log("Selection enforced", "info", this.toString());
+};
+
+/**
+ * Whether or not user-typed value in the text input box matches any of the
+ * query results.
+ *
+ * @method _textMatchesOption
+ * @return {HTMLElement} Matching list item element if user-input text matches
+ * a result, null otherwise.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._textMatchesOption = function() {
+ var foundMatch = null;
+
+ for(var i = this._nDisplayedItems-1; i >= 0 ; i--) {
+ var oItem = this._aListItems[i];
+ var sMatch = oItem._sResultKey.toLowerCase();
+ if(sMatch == this._sCurQuery.toLowerCase()) {
+ foundMatch = oItem;
+ break;
+ }
+ }
+ return(foundMatch);
+};
+
+/**
+ * Updates in the text input box with the first query result as the user types,
+ * selecting the substring that the user has not typed.
+ *
+ * @method _typeAhead
+ * @param oItem {HTMLElement} The <li> element item whose data populates the input field.
+ * @param sQuery {String} Query string.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._typeAhead = function(oItem, sQuery) {
+ // Don't update if turned off
+ if(!this.typeAhead || (this._nKeyCode == 8)) {
+ return;
+ }
+
+ var elTextbox = this._elTextbox;
+ var sValue = this._elTextbox.value; // any saved queries plus what user has typed
+
+ // Don't update with type-ahead if text selection is not supported
+ if(!elTextbox.setSelectionRange && !elTextbox.createTextRange) {
+ return;
+ }
+
+ // Select the portion of text that the user has not typed
+ var nStart = sValue.length;
+ this._updateValue(oItem);
+ var nEnd = elTextbox.value.length;
+ this._selectText(elTextbox,nStart,nEnd);
+ var sPrefill = elTextbox.value.substr(nStart,nEnd);
+ this.typeAheadEvent.fire(this,sQuery,sPrefill);
+ YAHOO.log("Typeahead occured with prefill string \"" + sPrefill + "\"", "info", this.toString());
+};
+
+/**
+ * Selects text in the input field.
+ *
+ * @method _selectText
+ * @param elTextbox {HTMLElement} Text input box element in which to select text.
+ * @param nStart {Number} Starting index of text string to select.
+ * @param nEnd {Number} Ending index of text selection.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._selectText = function(elTextbox, nStart, nEnd) {
+ if(elTextbox.setSelectionRange) { // For Mozilla
+ elTextbox.setSelectionRange(nStart,nEnd);
+ }
+ else if(elTextbox.createTextRange) { // For IE
+ var oTextRange = elTextbox.createTextRange();
+ oTextRange.moveStart("character", nStart);
+ oTextRange.moveEnd("character", nEnd-elTextbox.value.length);
+ oTextRange.select();
+ }
+ else {
+ elTextbox.select();
+ }
+};
+
+/**
+ * Syncs results container with its helpers.
+ *
+ * @method _toggleContainerHelpers
+ * @param bShow {Boolean} True if container is expanded, false if collapsed
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers = function(bShow) {
+ var bFireEvent = false;
+ var width = this._elContent.offsetWidth + "px";
+ var height = this._elContent.offsetHeight + "px";
+
+ if(this.useIFrame && this._elIFrame) {
+ bFireEvent = true;
+ if(bShow) {
+ this._elIFrame.style.width = width;
+ this._elIFrame.style.height = height;
+ }
+ else {
+ this._elIFrame.style.width = 0;
+ this._elIFrame.style.height = 0;
+ }
+ }
+ if(this.useShadow && this._elShadow) {
+ bFireEvent = true;
+ if(bShow) {
+ this._elShadow.style.width = width;
+ this._elShadow.style.height = height;
+ }
+ else {
+ this._elShadow.style.width = 0;
+ this._elShadow.style.height = 0;
+ }
+ }
+};
+
+/**
+ * Animates expansion or collapse of the container.
+ *
+ * @method _toggleContainer
+ * @param bShow {Boolean} True if container should be expanded, false if container should be collapsed
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._toggleContainer = function(bShow) {
+ var elContainer = this._elContainer;
+
+ // Implementer has container always open so don't mess with it
+ if(this.alwaysShowContainer && this._bContainerOpen) {
+ return;
+ }
+
+ // Clear contents of container
+ if(!bShow) {
+ this._elContent.scrollTop = 0;
+ var aItems = this._aListItems;
+
+ if(aItems && (aItems.length > 0)) {
+ for(var i = aItems.length-1; i >= 0 ; i--) {
+ aItems[i].style.display = "none";
+ }
+ }
+
+ if(this._oCurItem) {
+ this._toggleHighlight(this._oCurItem,"from");
+ }
+
+ this._oCurItem = null;
+ this._nDisplayedItems = 0;
+ this._sCurQuery = null;
+ }
+
+ // Container is already closed
+ if(!bShow && !this._bContainerOpen) {
+ this._elContent.style.display = "none";
+ return;
+ }
+
+ // If animation is enabled...
+ var oAnim = this._oAnim;
+ if(oAnim && oAnim.getEl() && (this.animHoriz || this.animVert)) {
+ // If helpers need to be collapsed, do it right away...
+ // but if helpers need to be expanded, wait until after the container expands
+ if(!bShow) {
+ this._toggleContainerHelpers(bShow);
+ }
+
+ if(oAnim.isAnimated()) {
+ oAnim.stop();
+ }
+
+ // Clone container to grab current size offscreen
+ var oClone = this._elContent.cloneNode(true);
+ elContainer.appendChild(oClone);
+ oClone.style.top = "-9000px";
+ oClone.style.display = "block";
+
+ // Current size of the container is the EXPANDED size
+ var wExp = oClone.offsetWidth;
+ var hExp = oClone.offsetHeight;
+
+ // Calculate COLLAPSED sizes based on horiz and vert anim
+ var wColl = (this.animHoriz) ? 0 : wExp;
+ var hColl = (this.animVert) ? 0 : hExp;
+
+ // Set animation sizes
+ oAnim.attributes = (bShow) ?
+ {width: { to: wExp }, height: { to: hExp }} :
+ {width: { to: wColl}, height: { to: hColl }};
+
+ // If opening anew, set to a collapsed size...
+ if(bShow && !this._bContainerOpen) {
+ this._elContent.style.width = wColl+"px";
+ this._elContent.style.height = hColl+"px";
+ }
+ // Else, set it to its last known size.
+ else {
+ this._elContent.style.width = wExp+"px";
+ this._elContent.style.height = hExp+"px";
+ }
+
+ elContainer.removeChild(oClone);
+ oClone = null;
+
+ var oSelf = this;
+ var onAnimComplete = function() {
+ // Finish the collapse
+ oAnim.onComplete.unsubscribeAll();
+
+ if(bShow) {
+ oSelf.containerExpandEvent.fire(oSelf);
+ YAHOO.log("Container expanded", "info", oSelf.toString());
+ }
+ else {
+ oSelf._elContent.style.display = "none";
+ oSelf.containerCollapseEvent.fire(oSelf);
+ YAHOO.log("Container collapsed", "info", oSelf.toString());
+ }
+ oSelf._toggleContainerHelpers(bShow);
+ };
+
+ // Display container and animate it
+ this._elContent.style.display = "block";
+ oAnim.onComplete.subscribe(onAnimComplete);
+ oAnim.animate();
+ this._bContainerOpen = bShow;
+ }
+ // Else don't animate, just show or hide
+ else {
+ if(bShow) {
+ this._elContent.style.display = "block";
+ this.containerExpandEvent.fire(this);
+ YAHOO.log("Container expanded", "info", this.toString());
+ }
+ else {
+ this._elContent.style.display = "none";
+ this.containerCollapseEvent.fire(this);
+ YAHOO.log("Container collapsed", "info", this.toString());
+ }
+ this._toggleContainerHelpers(bShow);
+ this._bContainerOpen = bShow;
+ }
+
+};
+
+/**
+ * Toggles the highlight on or off for an item in the container, and also cleans
+ * up highlighting of any previous item.
+ *
+ * @method _toggleHighlight
+ * @param oNewItem {HTMLElement} The <li> element item to receive highlight behavior.
+ * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._toggleHighlight = function(oNewItem, sType) {
+ var sHighlight = this.highlightClassName;
+ if(this._oCurItem) {
+ // Remove highlight from old item
+ YAHOO.util.Dom.removeClass(this._oCurItem, sHighlight);
+ }
+
+ if((sType == "to") && sHighlight) {
+ // Apply highlight to new item
+ YAHOO.util.Dom.addClass(oNewItem, sHighlight);
+ this._oCurItem = oNewItem;
+ }
+};
+
+/**
+ * Toggles the pre-highlight on or off for an item in the container.
+ *
+ * @method _togglePrehighlight
+ * @param oNewItem {HTMLElement} The <li> element item to receive highlight behavior.
+ * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._togglePrehighlight = function(oNewItem, sType) {
+ if(oNewItem == this._oCurItem) {
+ return;
+ }
+
+ var sPrehighlight = this.prehighlightClassName;
+ if((sType == "mouseover") && sPrehighlight) {
+ // Apply prehighlight to new item
+ YAHOO.util.Dom.addClass(oNewItem, sPrehighlight);
+ }
+ else {
+ // Remove prehighlight from old item
+ YAHOO.util.Dom.removeClass(oNewItem, sPrehighlight);
+ }
+};
+
+/**
+ * Updates the text input box value with selected query result. If a delimiter
+ * has been defined, then the value gets appended with the delimiter.
+ *
+ * @method _updateValue
+ * @param oItem {HTMLElement} The <li> element item with which to update the value.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._updateValue = function(oItem) {
+ var elTextbox = this._elTextbox;
+ var sDelimChar = (this.delimChar) ? (this.delimChar[0] || this.delimChar) : null;
+ var sSavedQuery = this._sSavedQuery;
+ var sResultKey = oItem._sResultKey;
+ elTextbox.focus();
+
+ // First clear text field
+ elTextbox.value = "";
+ // Grab data to put into text field
+ if(sDelimChar) {
+ if(sSavedQuery) {
+ elTextbox.value = sSavedQuery;
+ }
+ elTextbox.value += sResultKey + sDelimChar;
+ if(sDelimChar != " ") {
+ elTextbox.value += " ";
+ }
+ }
+ else { elTextbox.value = sResultKey; }
+
+ // scroll to bottom of textarea if necessary
+ if(elTextbox.type == "textarea") {
+ elTextbox.scrollTop = elTextbox.scrollHeight;
+ }
+
+ // move cursor to end
+ var end = elTextbox.value.length;
+ this._selectText(elTextbox,end,end);
+
+ this._oCurItem = oItem;
+};
+
+/**
+ * Selects a result item from the container
+ *
+ * @method _selectItem
+ * @param oItem {HTMLElement} The selected <li> element item.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._selectItem = function(oItem) {
+ this._bItemSelected = true;
+ this._updateValue(oItem);
+ this._cancelIntervalDetection(this);
+ this.itemSelectEvent.fire(this, oItem, oItem._oResultData);
+ YAHOO.log("Item selected: " + YAHOO.lang.dump(oItem._oResultData), "info", this.toString());
+ this._toggleContainer(false);
+};
+
+/**
+ * If an item is highlighted in the container, the right arrow key jumps to the
+ * end of the textbox and selects the highlighted item, otherwise the container
+ * is closed.
+ *
+ * @method _jumpSelection
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._jumpSelection = function() {
+ if(this._oCurItem) {
+ this._selectItem(this._oCurItem);
+ }
+ else {
+ this._toggleContainer(false);
+ }
+};
+
+/**
+ * Triggered by up and down arrow keys, changes the current highlighted
+ * <li> element item. Scrolls container if necessary.
+ *
+ * @method _moveSelection
+ * @param nKeyCode {Number} Code of key pressed.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._moveSelection = function(nKeyCode) {
+ if(this._bContainerOpen) {
+ // Determine current item's id number
+ var oCurItem = this._oCurItem;
+ var nCurItemIndex = -1;
+
+ if(oCurItem) {
+ nCurItemIndex = oCurItem._nItemIndex;
+ }
+
+ var nNewItemIndex = (nKeyCode == 40) ?
+ (nCurItemIndex + 1) : (nCurItemIndex - 1);
+
+ // Out of bounds
+ if(nNewItemIndex < -2 || nNewItemIndex >= this._nDisplayedItems) {
+ return;
+ }
+
+ if(oCurItem) {
+ // Unhighlight current item
+ this._toggleHighlight(oCurItem, "from");
+ this.itemArrowFromEvent.fire(this, oCurItem);
+ YAHOO.log("Item arrowed from", "info", this.toString());
+ }
+ if(nNewItemIndex == -1) {
+ // Go back to query (remove type-ahead string)
+ if(this.delimChar && this._sSavedQuery) {
+ if(!this._textMatchesOption()) {
+ this._elTextbox.value = this._sSavedQuery;
+ }
+ else {
+ this._elTextbox.value = this._sSavedQuery + this._sCurQuery;
+ }
+ }
+ else {
+ this._elTextbox.value = this._sCurQuery;
+ }
+ this._oCurItem = null;
+ return;
+ }
+ if(nNewItemIndex == -2) {
+ // Close container
+ this._toggleContainer(false);
+ return;
+ }
+
+ var oNewItem = this._aListItems[nNewItemIndex];
+
+ // Scroll the container if necessary
+ var elContent = this._elContent;
+ var scrollOn = ((YAHOO.util.Dom.getStyle(elContent,"overflow") == "auto") ||
+ (YAHOO.util.Dom.getStyle(elContent,"overflowY") == "auto"));
+ if(scrollOn && (nNewItemIndex > -1) &&
+ (nNewItemIndex < this._nDisplayedItems)) {
+ // User is keying down
+ if(nKeyCode == 40) {
+ // Bottom of selected item is below scroll area...
+ if((oNewItem.offsetTop+oNewItem.offsetHeight) > (elContent.scrollTop + elContent.offsetHeight)) {
+ // Set bottom of scroll area to bottom of selected item
+ elContent.scrollTop = (oNewItem.offsetTop+oNewItem.offsetHeight) - elContent.offsetHeight;
+ }
+ // Bottom of selected item is above scroll area...
+ else if((oNewItem.offsetTop+oNewItem.offsetHeight) < elContent.scrollTop) {
+ // Set top of selected item to top of scroll area
+ elContent.scrollTop = oNewItem.offsetTop;
+
+ }
+ }
+ // User is keying up
+ else {
+ // Top of selected item is above scroll area
+ if(oNewItem.offsetTop < elContent.scrollTop) {
+ // Set top of scroll area to top of selected item
+ this._elContent.scrollTop = oNewItem.offsetTop;
+ }
+ // Top of selected item is below scroll area
+ else if(oNewItem.offsetTop > (elContent.scrollTop + elContent.offsetHeight)) {
+ // Set bottom of selected item to bottom of scroll area
+ this._elContent.scrollTop = (oNewItem.offsetTop+oNewItem.offsetHeight) - elContent.offsetHeight;
+ }
+ }
+ }
+
+ this._toggleHighlight(oNewItem, "to");
+ this.itemArrowToEvent.fire(this, oNewItem);
+ YAHOO.log("Item arrowed to", "info", this.toString());
+ if(this.typeAhead) {
+ this._updateValue(oNewItem);
+ }
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private event handlers
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Handles <li> element mouseover events in the container.
+ *
+ * @method _onItemMouseover
+ * @param v {HTMLEvent} The mouseover event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onItemMouseover = function(v,oSelf) {
+ if(oSelf.prehighlightClassName) {
+ oSelf._togglePrehighlight(this,"mouseover");
+ }
+ else {
+ oSelf._toggleHighlight(this,"to");
+ }
+
+ oSelf.itemMouseOverEvent.fire(oSelf, this);
+ YAHOO.log("Item moused over", "info", oSelf.toString());
+};
+
+/**
+ * Handles <li> element mouseout events in the container.
+ *
+ * @method _onItemMouseout
+ * @param v {HTMLEvent} The mouseout event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onItemMouseout = function(v,oSelf) {
+ if(oSelf.prehighlightClassName) {
+ oSelf._togglePrehighlight(this,"mouseout");
+ }
+ else {
+ oSelf._toggleHighlight(this,"from");
+ }
+
+ oSelf.itemMouseOutEvent.fire(oSelf, this);
+ YAHOO.log("Item moused out", "info", oSelf.toString());
+};
+
+/**
+ * Handles <li> element click events in the container.
+ *
+ * @method _onItemMouseclick
+ * @param v {HTMLEvent} The click event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onItemMouseclick = function(v,oSelf) {
+ // In case item has not been moused over
+ oSelf._toggleHighlight(this,"to");
+ oSelf._selectItem(this);
+};
+
+/**
+ * Handles container mouseover events.
+ *
+ * @method _onContainerMouseover
+ * @param v {HTMLEvent} The mouseover event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onContainerMouseover = function(v,oSelf) {
+ oSelf._bOverContainer = true;
+};
+
+/**
+ * Handles container mouseout events.
+ *
+ * @method _onContainerMouseout
+ * @param v {HTMLEvent} The mouseout event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onContainerMouseout = function(v,oSelf) {
+ oSelf._bOverContainer = false;
+ // If container is still active
+ if(oSelf._oCurItem) {
+ oSelf._toggleHighlight(oSelf._oCurItem,"to");
+ }
+};
+
+/**
+ * Handles container scroll events.
+ *
+ * @method _onContainerScroll
+ * @param v {HTMLEvent} The scroll event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onContainerScroll = function(v,oSelf) {
+ oSelf._elTextbox.focus();
+};
+
+/**
+ * Handles container resize events.
+ *
+ * @method _onContainerResize
+ * @param v {HTMLEvent} The resize event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onContainerResize = function(v,oSelf) {
+ oSelf._toggleContainerHelpers(oSelf._bContainerOpen);
+};
+
+
+/**
+ * Handles textbox keydown events of functional keys, mainly for UI behavior.
+ *
+ * @method _onTextboxKeyDown
+ * @param v {HTMLEvent} The keydown event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown = function(v,oSelf) {
+ var nKeyCode = v.keyCode;
+
+ switch (nKeyCode) {
+ case 9: // tab
+ if((navigator.userAgent.toLowerCase().indexOf("mac") == -1)) {
+ // select an item or clear out
+ if(oSelf._oCurItem) {
+ if(oSelf.delimChar && (oSelf._nKeyCode != nKeyCode)) {
+ if(oSelf._bContainerOpen) {
+ YAHOO.util.Event.stopEvent(v);
+ }
+ }
+ oSelf._selectItem(oSelf._oCurItem);
+ }
+ else {
+ oSelf._toggleContainer(false);
+ }
+ }
+ break;
+ case 13: // enter
+ if((navigator.userAgent.toLowerCase().indexOf("mac") == -1)) {
+ if(oSelf._oCurItem) {
+ if(oSelf._nKeyCode != nKeyCode) {
+ if(oSelf._bContainerOpen) {
+ YAHOO.util.Event.stopEvent(v);
+ }
+ }
+ oSelf._selectItem(oSelf._oCurItem);
+ }
+ else {
+ oSelf._toggleContainer(false);
+ }
+ }
+ break;
+ case 27: // esc
+ oSelf._toggleContainer(false);
+ return;
+ case 39: // right
+ oSelf._jumpSelection();
+ break;
+ case 38: // up
+ YAHOO.util.Event.stopEvent(v);
+ oSelf._moveSelection(nKeyCode);
+ break;
+ case 40: // down
+ YAHOO.util.Event.stopEvent(v);
+ oSelf._moveSelection(nKeyCode);
+ break;
+ default:
+ break;
+ }
+};
+
+/**
+ * Handles textbox keypress events.
+ * @method _onTextboxKeyPress
+ * @param v {HTMLEvent} The keypress event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress = function(v,oSelf) {
+ var nKeyCode = v.keyCode;
+
+ //Expose only to Mac browsers, where stopEvent is ineffective on keydown events (bug 790337)
+ if((navigator.userAgent.toLowerCase().indexOf("mac") != -1)) {
+ switch (nKeyCode) {
+ case 9: // tab
+ // select an item or clear out
+ if(oSelf._oCurItem) {
+ if(oSelf.delimChar && (oSelf._nKeyCode != nKeyCode)) {
+ if(oSelf._bContainerOpen) {
+ YAHOO.util.Event.stopEvent(v);
+ }
+ }
+ oSelf._selectItem(oSelf._oCurItem);
+ }
+ else {
+ oSelf._toggleContainer(false);
+ }
+ break;
+ case 13: // enter
+ if(oSelf._oCurItem) {
+ if(oSelf._nKeyCode != nKeyCode) {
+ if(oSelf._bContainerOpen) {
+ YAHOO.util.Event.stopEvent(v);
+ }
+ }
+ oSelf._selectItem(oSelf._oCurItem);
+ }
+ else {
+ oSelf._toggleContainer(false);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ //TODO: (?) limit only to non-IE, non-Mac-FF for Korean IME support (bug 811948)
+ // Korean IME detected
+ else if(nKeyCode == 229) {
+ oSelf._queryInterval = setInterval(function() { oSelf._onIMEDetected(oSelf); },500);
+ }
+};
+
+/**
+ * Handles textbox keyup events that trigger queries.
+ *
+ * @method _onTextboxKeyUp
+ * @param v {HTMLEvent} The keyup event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp = function(v,oSelf) {
+ // Check to see if any of the public properties have been updated
+ oSelf._initProps();
+
+ var nKeyCode = v.keyCode;
+
+ oSelf._nKeyCode = nKeyCode;
+ var sText = this.value; //string in textbox
+
+ // Filter out chars that don't trigger queries
+ if(oSelf._isIgnoreKey(nKeyCode) || (sText.toLowerCase() == oSelf._sCurQuery)) {
+ return;
+ }
+ else {
+ oSelf._bItemSelected = false;
+ YAHOO.util.Dom.removeClass(oSelf._oCurItem, oSelf.highlightClassName);
+ oSelf._oCurItem = null;
+
+ oSelf.textboxKeyEvent.fire(oSelf, nKeyCode);
+ YAHOO.log("Textbox keyed", "info", oSelf.toString());
+ }
+
+ // Set timeout on the request
+ if(oSelf.queryDelay > 0) {
+ var nDelayID =
+ setTimeout(function(){oSelf._sendQuery(sText);},(oSelf.queryDelay * 1000));
+
+ if(oSelf._nDelayID != -1) {
+ clearTimeout(oSelf._nDelayID);
+ }
+
+ oSelf._nDelayID = nDelayID;
+ }
+ else {
+ // No delay so send request immediately
+ oSelf._sendQuery(sText);
+ }
+};
+
+/**
+ * Handles text input box receiving focus.
+ *
+ * @method _onTextboxFocus
+ * @param v {HTMLEvent} The focus event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onTextboxFocus = function (v,oSelf) {
+ oSelf._elTextbox.setAttribute("autocomplete","off");
+ oSelf._bFocused = true;
+ if(!oSelf._bItemSelected) {
+ oSelf.textboxFocusEvent.fire(oSelf);
+ YAHOO.log("Textbox focused", "info", oSelf.toString());
+ }
+};
+
+/**
+ * Handles text input box losing focus.
+ *
+ * @method _onTextboxBlur
+ * @param v {HTMLEvent} The focus event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onTextboxBlur = function (v,oSelf) {
+ // Don't treat as a blur if it was a selection via mouse click
+ if(!oSelf._bOverContainer || (oSelf._nKeyCode == 9)) {
+ // Current query needs to be validated as a selection
+ if(!oSelf._bItemSelected) {
+ var oMatch = oSelf._textMatchesOption();
+ // Container is closed or current query doesn't match any result
+ if(!oSelf._bContainerOpen || (oSelf._bContainerOpen && (oMatch === null))) {
+ // Force selection is enabled so clear the current query
+ if(oSelf.forceSelection) {
+ oSelf._clearSelection();
+ }
+ // Treat current query as a valid selection
+ else {
+ oSelf.unmatchedItemSelectEvent.fire(oSelf);
+ YAHOO.log("Unmatched item selected", "info", oSelf.toString());
+ }
+ }
+ // Container is open and current query matches a result
+ else {
+ // Force a selection when textbox is blurred with a match
+ if(oSelf.forceSelection) {
+ oSelf._selectItem(oMatch);
+ }
+ }
+ }
+
+ if(oSelf._bContainerOpen) {
+ oSelf._toggleContainer(false);
+ }
+ oSelf._cancelIntervalDetection(oSelf);
+ oSelf._bFocused = false;
+ oSelf.textboxBlurEvent.fire(oSelf);
+ YAHOO.log("Textbox blurred", "info", oSelf.toString());
+ }
+};
+
+/**
+ * Handles window unload event.
+ *
+ * @method _onWindowUnload
+ * @param v {HTMLEvent} The unload event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onWindowUnload = function(v,oSelf) {
+ if(oSelf && oSelf._elTextbox && oSelf.allowBrowserAutocomplete) {
+ oSelf._elTextbox.setAttribute("autocomplete","on");
+ }
+};
+
+/****************************************************************************/
+/****************************************************************************/
+/****************************************************************************/
+
+/**
+ * The DataSource classes manages sending a request and returning response from a live
+ * database. Supported data include local JavaScript arrays and objects and databases
+ * accessible via XHR connections. Supported response formats include JavaScript arrays,
+ * JSON, XML, and flat-file textual data.
+ *
+ * @class DataSource
+ * @constructor
+ */
+YAHOO.widget.DataSource = function() {
+ /* abstract class */
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public constants
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Error message for null data responses.
+ *
+ * @property ERROR_DATANULL
+ * @type String
+ * @static
+ * @final
+ */
+YAHOO.widget.DataSource.ERROR_DATANULL = "Response data was null";
+
+/**
+ * Error message for data responses with parsing errors.
+ *
+ * @property ERROR_DATAPARSE
+ * @type String
+ * @static
+ * @final
+ */
+YAHOO.widget.DataSource.ERROR_DATAPARSE = "Response data could not be parsed";
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Max size of the local cache. Set to 0 to turn off caching. Caching is
+ * useful to reduce the number of server connections. Recommended only for data
+ * sources that return comprehensive results for queries or when stale data is
+ * not an issue.
+ *
+ * @property maxCacheEntries
+ * @type Number
+ * @default 15
+ */
+YAHOO.widget.DataSource.prototype.maxCacheEntries = 15;
+
+/**
+ * Use this to fine-tune the matching algorithm used against JS Array types of
+ * DataSource and DataSource caches. If queryMatchContains is true, then the JS
+ * Array or cache returns results that "contain" the query string. By default,
+ * queryMatchContains is set to false, so that only results that "start with"
+ * the query string are returned.
+ *
+ * @property queryMatchContains
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.DataSource.prototype.queryMatchContains = false;
+
+/**
+ * Enables query subset matching. If caching is on and queryMatchSubset is
+ * true, substrings of queries will return matching cached results. For
+ * instance, if the first query is for "abc" susequent queries that start with
+ * "abc", like "abcd", will be queried against the cache, and not the live data
+ * source. Recommended only for DataSources that return comprehensive results
+ * for queries with very few characters.
+ *
+ * @property queryMatchSubset
+ * @type Boolean
+ * @default false
+ *
+ */
+YAHOO.widget.DataSource.prototype.queryMatchSubset = false;
+
+/**
+ * Enables case-sensitivity in the matching algorithm used against JS Array
+ * types of DataSources and DataSource caches. If queryMatchCase is true, only
+ * case-sensitive matches will return.
+ *
+ * @property queryMatchCase
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.DataSource.prototype.queryMatchCase = false;
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Public accessor to the unique name of the DataSource instance.
+ *
+ * @method toString
+ * @return {String} Unique name of the DataSource instance
+ */
+YAHOO.widget.DataSource.prototype.toString = function() {
+ return "DataSource " + this._sName;
+};
+
+/**
+ * Retrieves query results, first checking the local cache, then making the
+ * query request to the live data source as defined by the function doQuery.
+ *
+ * @method getResults
+ * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
+ * @param sQuery {String} Query string.
+ * @param oParent {Object} The object instance that has requested data.
+ */
+YAHOO.widget.DataSource.prototype.getResults = function(oCallbackFn, sQuery, oParent) {
+
+ // First look in cache
+ var aResults = this._doQueryCache(oCallbackFn,sQuery,oParent);
+ // Not in cache, so get results from server
+ if(aResults.length === 0) {
+ this.queryEvent.fire(this, oParent, sQuery);
+ YAHOO.log("Query received \"" + sQuery, "info", this.toString());
+ this.doQuery(oCallbackFn, sQuery, oParent);
+ }
+};
+
+/**
+ * Abstract method implemented by subclasses to make a query to the live data
+ * source. Must call the callback function with the response returned from the
+ * query. Populates cache (if enabled).
+ *
+ * @method doQuery
+ * @param oCallbackFn {HTMLFunction} Callback function implemented by oParent to which to return results.
+ * @param sQuery {String} Query string.
+ * @param oParent {Object} The object instance that has requested data.
+ */
+YAHOO.widget.DataSource.prototype.doQuery = function(oCallbackFn, sQuery, oParent) {
+ /* override this */
+};
+
+/**
+ * Flushes cache.
+ *
+ * @method flushCache
+ */
+YAHOO.widget.DataSource.prototype.flushCache = function() {
+ if(this._aCache) {
+ this._aCache = [];
+ }
+ if(this._aCacheHelper) {
+ this._aCacheHelper = [];
+ }
+ this.cacheFlushEvent.fire(this);
+ YAHOO.log("Cache flushed", "info", this.toString());
+
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public events
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Fired when a query is made to the live data source.
+ *
+ * @event queryEvent
+ * @param oSelf {Object} The DataSource instance.
+ * @param oParent {Object} The requesting object.
+ * @param sQuery {String} The query string.
+ */
+YAHOO.widget.DataSource.prototype.queryEvent = null;
+
+/**
+ * Fired when a query is made to the local cache.
+ *
+ * @event cacheQueryEvent
+ * @param oSelf {Object} The DataSource instance.
+ * @param oParent {Object} The requesting object.
+ * @param sQuery {String} The query string.
+ */
+YAHOO.widget.DataSource.prototype.cacheQueryEvent = null;
+
+/**
+ * Fired when data is retrieved from the live data source.
+ *
+ * @event getResultsEvent
+ * @param oSelf {Object} The DataSource instance.
+ * @param oParent {Object} The requesting object.
+ * @param sQuery {String} The query string.
+ * @param aResults {Object[]} Array of result objects.
+ */
+YAHOO.widget.DataSource.prototype.getResultsEvent = null;
+
+/**
+ * Fired when data is retrieved from the local cache.
+ *
+ * @event getCachedResultsEvent
+ * @param oSelf {Object} The DataSource instance.
+ * @param oParent {Object} The requesting object.
+ * @param sQuery {String} The query string.
+ * @param aResults {Object[]} Array of result objects.
+ */
+YAHOO.widget.DataSource.prototype.getCachedResultsEvent = null;
+
+/**
+ * Fired when an error is encountered with the live data source.
+ *
+ * @event dataErrorEvent
+ * @param oSelf {Object} The DataSource instance.
+ * @param oParent {Object} The requesting object.
+ * @param sQuery {String} The query string.
+ * @param sMsg {String} Error message string
+ */
+YAHOO.widget.DataSource.prototype.dataErrorEvent = null;
+
+/**
+ * Fired when the local cache is flushed.
+ *
+ * @event cacheFlushEvent
+ * @param oSelf {Object} The DataSource instance
+ */
+YAHOO.widget.DataSource.prototype.cacheFlushEvent = null;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Internal class variable to index multiple DataSource instances.
+ *
+ * @property _nIndex
+ * @type Number
+ * @private
+ * @static
+ */
+YAHOO.widget.DataSource._nIndex = 0;
+
+/**
+ * Name of DataSource instance.
+ *
+ * @property _sName
+ * @type String
+ * @private
+ */
+YAHOO.widget.DataSource.prototype._sName = null;
+
+/**
+ * Local cache of data result objects indexed chronologically.
+ *
+ * @property _aCache
+ * @type Object[]
+ * @private
+ */
+YAHOO.widget.DataSource.prototype._aCache = null;
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes DataSource instance.
+ *
+ * @method _init
+ * @private
+ */
+YAHOO.widget.DataSource.prototype._init = function() {
+ // Validate and initialize public configs
+ var maxCacheEntries = this.maxCacheEntries;
+ if(!YAHOO.lang.isNumber(maxCacheEntries) || (maxCacheEntries < 0)) {
+ maxCacheEntries = 0;
+ }
+ // Initialize local cache
+ if(maxCacheEntries > 0 && !this._aCache) {
+ this._aCache = [];
+ }
+
+ this._sName = "instance" + YAHOO.widget.DataSource._nIndex;
+ YAHOO.widget.DataSource._nIndex++;
+
+ this.queryEvent = new YAHOO.util.CustomEvent("query", this);
+ this.cacheQueryEvent = new YAHOO.util.CustomEvent("cacheQuery", this);
+ this.getResultsEvent = new YAHOO.util.CustomEvent("getResults", this);
+ this.getCachedResultsEvent = new YAHOO.util.CustomEvent("getCachedResults", this);
+ this.dataErrorEvent = new YAHOO.util.CustomEvent("dataError", this);
+ this.cacheFlushEvent = new YAHOO.util.CustomEvent("cacheFlush", this);
+};
+
+/**
+ * Adds a result object to the local cache, evicting the oldest element if the
+ * cache is full. Newer items will have higher indexes, the oldest item will have
+ * index of 0.
+ *
+ * @method _addCacheElem
+ * @param oResult {Object} Data result object, including array of results.
+ * @private
+ */
+YAHOO.widget.DataSource.prototype._addCacheElem = function(oResult) {
+ var aCache = this._aCache;
+ // Don't add if anything important is missing.
+ if(!aCache || !oResult || !oResult.query || !oResult.results) {
+ return;
+ }
+
+ // If the cache is full, make room by removing from index=0
+ if(aCache.length >= this.maxCacheEntries) {
+ aCache.shift();
+ }
+
+ // Add to cache, at the end of the array
+ aCache.push(oResult);
+};
+
+/**
+ * Queries the local cache for results. If query has been cached, the callback
+ * function is called with the results, and the cached is refreshed so that it
+ * is now the newest element.
+ *
+ * @method _doQueryCache
+ * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
+ * @param sQuery {String} Query string.
+ * @param oParent {Object} The object instance that has requested data.
+ * @return aResults {Object[]} Array of results from local cache if found, otherwise null.
+ * @private
+ */
+YAHOO.widget.DataSource.prototype._doQueryCache = function(oCallbackFn, sQuery, oParent) {
+ var aResults = [];
+ var bMatchFound = false;
+ var aCache = this._aCache;
+ var nCacheLength = (aCache) ? aCache.length : 0;
+ var bMatchContains = this.queryMatchContains;
+ var sOrigQuery;
+
+ // If cache is enabled...
+ if((this.maxCacheEntries > 0) && aCache && (nCacheLength > 0)) {
+ this.cacheQueryEvent.fire(this, oParent, sQuery);
+ YAHOO.log("Querying cache: \"" + sQuery + "\"", "info", this.toString());
+ // If case is unimportant, normalize query now instead of in loops
+ if(!this.queryMatchCase) {
+ sOrigQuery = sQuery;
+ sQuery = sQuery.toLowerCase();
+ }
+
+ // Loop through each cached element's query property...
+ for(var i = nCacheLength-1; i >= 0; i--) {
+ var resultObj = aCache[i];
+ var aAllResultItems = resultObj.results;
+ // If case is unimportant, normalize match key for comparison
+ var matchKey = (!this.queryMatchCase) ?
+ encodeURIComponent(resultObj.query).toLowerCase():
+ encodeURIComponent(resultObj.query);
+
+ // If a cached match key exactly matches the query...
+ if(matchKey == sQuery) {
+ // Stash all result objects into aResult[] and stop looping through the cache.
+ bMatchFound = true;
+ aResults = aAllResultItems;
+
+ // The matching cache element was not the most recent,
+ // so now we need to refresh the cache.
+ if(i != nCacheLength-1) {
+ // Remove element from its original location
+ aCache.splice(i,1);
+ // Add element as newest
+ this._addCacheElem(resultObj);
+ }
+ break;
+ }
+ // Else if this query is not an exact match and subset matching is enabled...
+ else if(this.queryMatchSubset) {
+ // Loop through substrings of each cached element's query property...
+ for(var j = sQuery.length-1; j >= 0 ; j--) {
+ var subQuery = sQuery.substr(0,j);
+
+ // If a substring of a cached sQuery exactly matches the query...
+ if(matchKey == subQuery) {
+ bMatchFound = true;
+
+ // Go through each cached result object to match against the query...
+ for(var k = aAllResultItems.length-1; k >= 0; k--) {
+ var aRecord = aAllResultItems[k];
+ var sKeyIndex = (this.queryMatchCase) ?
+ encodeURIComponent(aRecord[0]).indexOf(sQuery):
+ encodeURIComponent(aRecord[0]).toLowerCase().indexOf(sQuery);
+
+ // A STARTSWITH match is when the query is found at the beginning of the key string...
+ if((!bMatchContains && (sKeyIndex === 0)) ||
+ // A CONTAINS match is when the query is found anywhere within the key string...
+ (bMatchContains && (sKeyIndex > -1))) {
+ // Stash a match into aResults[].
+ aResults.unshift(aRecord);
+ }
+ }
+
+ // Add the subset match result set object as the newest element to cache,
+ // and stop looping through the cache.
+ resultObj = {};
+ resultObj.query = sQuery;
+ resultObj.results = aResults;
+ this._addCacheElem(resultObj);
+ break;
+ }
+ }
+ if(bMatchFound) {
+ break;
+ }
+ }
+ }
+
+ // If there was a match, send along the results.
+ if(bMatchFound) {
+ this.getCachedResultsEvent.fire(this, oParent, sOrigQuery, aResults);
+ YAHOO.log("Cached results found for query \"" + sQuery + "\": " +
+ YAHOO.lang.dump(aResults), "info", this.toString());
+ oCallbackFn(sOrigQuery, aResults, oParent);
+ }
+ }
+ return aResults;
+};
+
+
+/****************************************************************************/
+/****************************************************************************/
+/****************************************************************************/
+
+/**
+ * Implementation of YAHOO.widget.DataSource using XML HTTP requests that return
+ * query results.
+ *
+ * @class DS_XHR
+ * @extends YAHOO.widget.DataSource
+ * @requires connection
+ * @constructor
+ * @param sScriptURI {String} Absolute or relative URI to script that returns query
+ * results as JSON, XML, or delimited flat-file data.
+ * @param aSchema {String[]} Data schema definition of results.
+ * @param oConfigs {Object} (optional) Object literal of config params.
+ */
+YAHOO.widget.DS_XHR = function(sScriptURI, aSchema, oConfigs) {
+ // Set any config params passed in to override defaults
+ if(oConfigs && (oConfigs.constructor == Object)) {
+ for(var sConfig in oConfigs) {
+ this[sConfig] = oConfigs[sConfig];
+ }
+ }
+
+ // Initialization sequence
+ if(!YAHOO.lang.isArray(aSchema) || !YAHOO.lang.isString(sScriptURI)) {
+ YAHOO.log("Could not instantiate XHR DataSource due to invalid arguments", "error", this.toString());
+ return;
+ }
+
+ this.schema = aSchema;
+ this.scriptURI = sScriptURI;
+
+ this._init();
+ YAHOO.log("XHR DataSource initialized","info",this.toString());
+};
+
+YAHOO.widget.DS_XHR.prototype = new YAHOO.widget.DataSource();
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public constants
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * JSON data type.
+ *
+ * @property TYPE_JSON
+ * @type Number
+ * @static
+ * @final
+ */
+YAHOO.widget.DS_XHR.TYPE_JSON = 0;
+
+/**
+ * XML data type.
+ *
+ * @property TYPE_XML
+ * @type Number
+ * @static
+ * @final
+ */
+YAHOO.widget.DS_XHR.TYPE_XML = 1;
+
+/**
+ * Flat-file data type.
+ *
+ * @property TYPE_FLAT
+ * @type Number
+ * @static
+ * @final
+ */
+YAHOO.widget.DS_XHR.TYPE_FLAT = 2;
+
+/**
+ * Error message for XHR failure.
+ *
+ * @property ERROR_DATAXHR
+ * @type String
+ * @static
+ * @final
+ */
+YAHOO.widget.DS_XHR.ERROR_DATAXHR = "XHR response failed";
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Alias to YUI Connection Manager, to allow implementers to customize the utility.
+ *
+ * @property connMgr
+ * @type Object
+ * @default YAHOO.util.Connect
+ */
+YAHOO.widget.DS_XHR.prototype.connMgr = YAHOO.util.Connect;
+
+/**
+ * Number of milliseconds the XHR connection will wait for a server response. A
+ * a value of zero indicates the XHR connection will wait forever. Any value
+ * greater than zero will use the Connection utility's Auto-Abort feature.
+ *
+ * @property connTimeout
+ * @type Number
+ * @default 0
+ */
+YAHOO.widget.DS_XHR.prototype.connTimeout = 0;
+
+/**
+ * Absolute or relative URI to script that returns query results. For instance,
+ * queries will be sent to <scriptURI>?<scriptQueryParam>=userinput
+ *
+ * @property scriptURI
+ * @type String
+ */
+YAHOO.widget.DS_XHR.prototype.scriptURI = null;
+
+/**
+ * Query string parameter name sent to scriptURI. For instance, queries will be
+ * sent to <scriptURI>?<scriptQueryParam>=userinput
+ *
+ * @property scriptQueryParam
+ * @type String
+ * @default "query"
+ */
+YAHOO.widget.DS_XHR.prototype.scriptQueryParam = "query";
+
+/**
+ * String of key/value pairs to append to requests made to scriptURI. Define
+ * this string when you want to send additional query parameters to your script.
+ * When defined, queries will be sent to
+ * <scriptURI>?<scriptQueryParam>=userinput&<scriptQueryAppend>
+ *
+ * @property scriptQueryAppend
+ * @type String
+ * @default ""
+ */
+YAHOO.widget.DS_XHR.prototype.scriptQueryAppend = "";
+
+/**
+ * XHR response data type. Other types that may be defined are YAHOO.widget.DS_XHR.TYPE_XML
+ * and YAHOO.widget.DS_XHR.TYPE_FLAT.
+ *
+ * @property responseType
+ * @type String
+ * @default YAHOO.widget.DS_XHR.TYPE_JSON
+ */
+YAHOO.widget.DS_XHR.prototype.responseType = YAHOO.widget.DS_XHR.TYPE_JSON;
+
+/**
+ * String after which to strip results. If the results from the XHR are sent
+ * back as HTML, the gzip HTML comment appears at the end of the data and should
+ * be ignored.
+ *
+ * @property responseStripAfter
+ * @type String
+ * @default "\n<!-"
+ */
+YAHOO.widget.DS_XHR.prototype.responseStripAfter = "\n 0) {
+ sUri += "&" + this.scriptQueryAppend;
+ }
+ YAHOO.log("DataSource is querying URL " + sUri, "info", this.toString());
+ var oResponse = null;
+
+ var oSelf = this;
+ /*
+ * Sets up ajax request callback
+ *
+ * @param {object} oReq HTTPXMLRequest object
+ * @private
+ */
+ var responseSuccess = function(oResp) {
+ // Response ID does not match last made request ID.
+ if(!oSelf._oConn || (oResp.tId != oSelf._oConn.tId)) {
+ oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATANULL);
+ YAHOO.log(YAHOO.widget.DataSource.ERROR_DATANULL, "error", oSelf.toString());
+ return;
+ }
+//DEBUG
+/*YAHOO.log(oResp.responseXML.getElementsByTagName("Result"),'warn');
+for(var foo in oResp) {
+ YAHOO.log(foo + ": "+oResp[foo],'warn');
+}
+YAHOO.log('responseXML.xml: '+oResp.responseXML.xml,'warn');*/
+ if(!isXML) {
+ oResp = oResp.responseText;
+ }
+ else {
+ oResp = oResp.responseXML;
+ }
+ if(oResp === null) {
+ oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATANULL);
+ YAHOO.log(YAHOO.widget.DataSource.ERROR_DATANULL, "error", oSelf.toString());
+ return;
+ }
+
+ var aResults = oSelf.parseResponse(sQuery, oResp, oParent);
+ var resultObj = {};
+ resultObj.query = decodeURIComponent(sQuery);
+ resultObj.results = aResults;
+ if(aResults === null) {
+ oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATAPARSE);
+ YAHOO.log(YAHOO.widget.DataSource.ERROR_DATAPARSE, "error", oSelf.toString());
+ aResults = [];
+ }
+ else {
+ oSelf.getResultsEvent.fire(oSelf, oParent, sQuery, aResults);
+ YAHOO.log("Results returned for query \"" + sQuery + "\": " +
+ YAHOO.lang.dump(aResults), "info", oSelf.toString());
+ oSelf._addCacheElem(resultObj);
+ }
+ oCallbackFn(sQuery, aResults, oParent);
+ };
+
+ var responseFailure = function(oResp) {
+ oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DS_XHR.ERROR_DATAXHR);
+ YAHOO.log(YAHOO.widget.DS_XHR.ERROR_DATAXHR + ": " + oResp.statusText, "error", oSelf.toString());
+ return;
+ };
+
+ var oCallback = {
+ success:responseSuccess,
+ failure:responseFailure
+ };
+
+ if(YAHOO.lang.isNumber(this.connTimeout) && (this.connTimeout > 0)) {
+ oCallback.timeout = this.connTimeout;
+ }
+
+ if(this._oConn) {
+ this.connMgr.abort(this._oConn);
+ }
+
+ oSelf._oConn = this.connMgr.asyncRequest("GET", sUri, oCallback, null);
+};
+
+/**
+ * Parses raw response data into an array of result objects. The result data key
+ * is always stashed in the [0] element of each result object.
+ *
+ * @method parseResponse
+ * @param sQuery {String} Query string.
+ * @param oResponse {Object} The raw response data to parse.
+ * @param oParent {Object} The object instance that has requested data.
+ * @returns {Object[]} Array of result objects.
+ */
+YAHOO.widget.DS_XHR.prototype.parseResponse = function(sQuery, oResponse, oParent) {
+ var aSchema = this.schema;
+ var aResults = [];
+ var bError = false;
+
+ // Strip out comment at the end of results
+ var nEnd = ((this.responseStripAfter !== "") && (oResponse.indexOf)) ?
+ oResponse.indexOf(this.responseStripAfter) : -1;
+ if(nEnd != -1) {
+ oResponse = oResponse.substring(0,nEnd);
+ }
+
+ switch (this.responseType) {
+ case YAHOO.widget.DS_XHR.TYPE_JSON:
+ var jsonList, jsonObjParsed;
+ // Check for YUI JSON
+ if(YAHOO.lang.JSON) {
+ // Use the JSON utility if available
+ jsonObjParsed = YAHOO.lang.JSON.parse(oResponse);
+ if(!jsonObjParsed) {
+ bError = true;
+ break;
+ }
+ else {
+ try {
+ // eval is necessary here since aSchema[0] is of unknown depth
+ jsonList = eval("jsonObjParsed." + aSchema[0]);
+ }
+ catch(e) {
+ bError = true;
+ break;
+ }
+ }
+ }
+ // Check for JSON lib
+ else if(oResponse.parseJSON) {
+ // Use the new JSON utility if available
+ jsonObjParsed = oResponse.parseJSON();
+ if(!jsonObjParsed) {
+ bError = true;
+ }
+ else {
+ try {
+ // eval is necessary here since aSchema[0] is of unknown depth
+ jsonList = eval("jsonObjParsed." + aSchema[0]);
+ }
+ catch(e) {
+ bError = true;
+ break;
+ }
+ }
+ }
+ // Use older JSON lib if available
+ else if(window.JSON) {
+ jsonObjParsed = JSON.parse(oResponse);
+ if(!jsonObjParsed) {
+ bError = true;
+ break;
+ }
+ else {
+ try {
+ // eval is necessary here since aSchema[0] is of unknown depth
+ jsonList = eval("jsonObjParsed." + aSchema[0]);
+ }
+ catch(e) {
+ bError = true;
+ break;
+ }
+ }
+ }
+ else {
+ // Parse the JSON response as a string
+ try {
+ // Trim leading spaces
+ while (oResponse.substring(0,1) == " ") {
+ oResponse = oResponse.substring(1, oResponse.length);
+ }
+
+ // Invalid JSON response
+ if(oResponse.indexOf("{") < 0) {
+ bError = true;
+ break;
+ }
+
+ // Empty (but not invalid) JSON response
+ if(oResponse.indexOf("{}") === 0) {
+ break;
+ }
+
+ // Turn the string into an object literal...
+ // ...eval is necessary here
+ var jsonObjRaw = eval("(" + oResponse + ")");
+ if(!jsonObjRaw) {
+ bError = true;
+ break;
+ }
+
+ // Grab the object member that contains an array of all reponses...
+ // ...eval is necessary here since aSchema[0] is of unknown depth
+ jsonList = eval("(jsonObjRaw." + aSchema[0]+")");
+ }
+ catch(e) {
+ bError = true;
+ break;
+ }
+ }
+
+ if(!jsonList) {
+ bError = true;
+ break;
+ }
+
+ if(!YAHOO.lang.isArray(jsonList)) {
+ jsonList = [jsonList];
+ }
+
+ // Loop through the array of all responses...
+ for(var i = jsonList.length-1; i >= 0 ; i--) {
+ var aResultItem = [];
+ var jsonResult = jsonList[i];
+ // ...and loop through each data field value of each response
+ for(var j = aSchema.length-1; j >= 1 ; j--) {
+ // ...and capture data into an array mapped according to the schema...
+ var dataFieldValue = jsonResult[aSchema[j]];
+ if(!dataFieldValue) {
+ dataFieldValue = "";
+ }
+ //YAHOO.log("data: " + i + " value:" +j+" = "+dataFieldValue,"debug",this.toString());
+ aResultItem.unshift(dataFieldValue);
+ }
+ // If schema isn't well defined, pass along the entire result object
+ if(aResultItem.length == 1) {
+ aResultItem.push(jsonResult);
+ }
+ // Capture the array of data field values in an array of results
+ aResults.unshift(aResultItem);
+ }
+ break;
+ case YAHOO.widget.DS_XHR.TYPE_XML:
+ // Get the collection of results
+ var xmlList = oResponse.getElementsByTagName(aSchema[0]);
+ if(!xmlList) {
+ bError = true;
+ break;
+ }
+ // Loop through each result
+ for(var k = xmlList.length-1; k >= 0 ; k--) {
+ var result = xmlList.item(k);
+ //YAHOO.log("Result"+k+" is "+result.attributes.item(0).firstChild.nodeValue,"debug",this.toString());
+ var aFieldSet = [];
+ // Loop through each data field in each result using the schema
+ for(var m = aSchema.length-1; m >= 1 ; m--) {
+ //YAHOO.log(aSchema[m]+" is "+result.attributes.getNamedItem(aSchema[m]).firstChild.nodeValue);
+ var sValue = null;
+ // Values may be held in an attribute...
+ var xmlAttr = result.attributes.getNamedItem(aSchema[m]);
+ if(xmlAttr) {
+ sValue = xmlAttr.value;
+ //YAHOO.log("Attr value is "+sValue,"debug",this.toString());
+ }
+ // ...or in a node
+ else{
+ var xmlNode = result.getElementsByTagName(aSchema[m]);
+ if(xmlNode && xmlNode.item(0) && xmlNode.item(0).firstChild) {
+ sValue = xmlNode.item(0).firstChild.nodeValue;
+ //YAHOO.log("Node value is "+sValue,"debug",this.toString());
+ }
+ else {
+ sValue = "";
+ //YAHOO.log("Value not found","debug",this.toString());
+ }
+ }
+ // Capture the schema-mapped data field values into an array
+ aFieldSet.unshift(sValue);
+ }
+ // Capture each array of values into an array of results
+ aResults.unshift(aFieldSet);
+ }
+ break;
+ case YAHOO.widget.DS_XHR.TYPE_FLAT:
+ if(oResponse.length > 0) {
+ // Delete the last line delimiter at the end of the data if it exists
+ var newLength = oResponse.length-aSchema[0].length;
+ if(oResponse.substr(newLength) == aSchema[0]) {
+ oResponse = oResponse.substr(0, newLength);
+ }
+ if(oResponse.length > 0) {
+ var aRecords = oResponse.split(aSchema[0]);
+ for(var n = aRecords.length-1; n >= 0; n--) {
+ if(aRecords[n].length > 0) {
+ aResults[n] = aRecords[n].split(aSchema[1]);
+ }
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ sQuery = null;
+ oResponse = null;
+ oParent = null;
+ if(bError) {
+ return null;
+ }
+ else {
+ return aResults;
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * XHR connection object.
+ *
+ * @property _oConn
+ * @type Object
+ * @private
+ */
+YAHOO.widget.DS_XHR.prototype._oConn = null;
+
+
+/****************************************************************************/
+/****************************************************************************/
+/****************************************************************************/
+
+/**
+ * Implementation of YAHOO.widget.DataSource using the Get Utility to generate
+ * dynamic SCRIPT nodes for data retrieval.
+ *
+ * @class DS_ScriptNode
+ * @constructor
+ * @extends YAHOO.widget.DataSource
+ * @param sUri {String} URI to the script location that will return data.
+ * @param aSchema {String[]} Data schema definition of results.
+ * @param oConfigs {Object} (optional) Object literal of config params.
+ */
+YAHOO.widget.DS_ScriptNode = function(sUri, aSchema, oConfigs) {
+ // Set any config params passed in to override defaults
+ if(oConfigs && (oConfigs.constructor == Object)) {
+ for(var sConfig in oConfigs) {
+ this[sConfig] = oConfigs[sConfig];
+ }
+ }
+
+ // Initialization sequence
+ if(!YAHOO.lang.isArray(aSchema) || !YAHOO.lang.isString(sUri)) {
+ YAHOO.log("Could not instantiate Script Node DataSource due to invalid arguments", "error", this.toString());
+ return;
+ }
+
+ this.schema = aSchema;
+ this.scriptURI = sUri;
+
+ this._init();
+ YAHOO.log("Script Node DataSource initialized","info",this.toString());
+};
+
+YAHOO.widget.DS_ScriptNode.prototype = new YAHOO.widget.DataSource();
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Alias to YUI Get Utility. Allows implementers to specify their own
+ * subclasses of the YUI Get Utility.
+ *
+ * @property getUtility
+ * @type Object
+ * @default YAHOO.util.Get
+ */
+YAHOO.widget.DS_ScriptNode.prototype.getUtility = YAHOO.util.Get;
+
+/**
+ * URI to the script that returns data.
+ *
+ * @property scriptURI
+ * @type String
+ */
+YAHOO.widget.DS_ScriptNode.prototype.scriptURI = null;
+
+/**
+ * Query string parameter name sent to scriptURI. For instance, requests will be
+ * sent to <scriptURI>?<scriptQueryParam>=queryString
+ *
+ * @property scriptQueryParam
+ * @type String
+ * @default "query"
+ */
+YAHOO.widget.DS_ScriptNode.prototype.scriptQueryParam = "query";
+
+/**
+ * Defines request/response management in the following manner:
+ *
+ *
+ *
ignoreStaleResponses
+ *
Send all requests, but handle only the response for the most recently sent request.
+ *
allowAll
+ *
Send all requests and handle all responses.
+ *
+ *
+ * @property asyncMode
+ * @type String
+ * @default "allowAll"
+ */
+YAHOO.widget.DS_ScriptNode.prototype.asyncMode = "allowAll";
+
+/**
+ * Callback string parameter name sent to scriptURI. For instance, requests will be
+ * sent to <scriptURI>?<scriptCallbackParam>=callbackFunction
+ *
+ * @property scriptCallbackParam
+ * @type String
+ * @default "callback"
+ */
+YAHOO.widget.DS_ScriptNode.prototype.scriptCallbackParam = "callback";
+
+/**
+ * Global array of callback functions, one for each request sent.
+ *
+ * @property callbacks
+ * @type Function[]
+ * @static
+ */
+YAHOO.widget.DS_ScriptNode.callbacks = [];
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Unique ID to track requests.
+ *
+ * @property _nId
+ * @type Number
+ * @private
+ * @static
+ */
+YAHOO.widget.DS_ScriptNode._nId = 0;
+
+/**
+ * Counter for pending requests. When this is 0, it is safe to purge callbacks
+ * array.
+ *
+ * @property _nPending
+ * @type Number
+ * @private
+ * @static
+ */
+YAHOO.widget.DS_ScriptNode._nPending = 0;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Queries the live data source. Results are passed back to a callback function.
+ *
+ * @method doQuery
+ * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
+ * @param sQuery {String} Query string.
+ * @param oParent {Object} The object instance that has requested data.
+ */
+YAHOO.widget.DS_ScriptNode.prototype.doQuery = function(oCallbackFn, sQuery, oParent) {
+ var oSelf = this;
+
+ // If there are no global pending requests, it is safe to purge global callback stack and global counter
+ if(YAHOO.widget.DS_ScriptNode._nPending === 0) {
+ YAHOO.widget.DS_ScriptNode.callbacks = [];
+ YAHOO.widget.DS_ScriptNode._nId = 0;
+ }
+
+ // ID for this request
+ var id = YAHOO.widget.DS_ScriptNode._nId;
+ YAHOO.widget.DS_ScriptNode._nId++;
+
+ // Dynamically add handler function with a closure to the callback stack
+ YAHOO.widget.DS_ScriptNode.callbacks[id] = function(oResponse) {
+ if((oSelf.asyncMode !== "ignoreStaleResponses")||
+ (id === YAHOO.widget.DS_ScriptNode.callbacks.length-1)) { // Must ignore stale responses
+ oSelf.handleResponse(oResponse, oCallbackFn, sQuery, oParent);
+ }
+ else {
+ YAHOO.log("DataSource ignored stale response for " + sQuery, "info", oSelf.toString());
+ }
+
+ delete YAHOO.widget.DS_ScriptNode.callbacks[id];
+ };
+
+ // We are now creating a request
+ YAHOO.widget.DS_ScriptNode._nPending++;
+
+ var sUri = this.scriptURI+"&"+ this.scriptQueryParam+"="+sQuery+"&"+
+ this.scriptCallbackParam+"=YAHOO.widget.DS_ScriptNode.callbacks["+id+"]";
+ YAHOO.log("DataSource is querying URL " + sUri, "info", this.toString());
+ this.getUtility.script(sUri,
+ {autopurge:true,
+ onsuccess:YAHOO.widget.DS_ScriptNode._bumpPendingDown,
+ onfail:YAHOO.widget.DS_ScriptNode._bumpPendingDown});
+};
+
+/**
+ * Parses JSON response data into an array of result objects and passes it to
+ * the callback function.
+ *
+ * @method handleResponse
+ * @param oResponse {Object} The raw response data to parse.
+ * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
+ * @param sQuery {String} Query string.
+ * @param oParent {Object} The object instance that has requested data.
+ */
+YAHOO.widget.DS_ScriptNode.prototype.handleResponse = function(oResponse, oCallbackFn, sQuery, oParent) {
+ var aSchema = this.schema;
+ var aResults = [];
+ var bError = false;
+
+ var jsonList, jsonObjParsed;
+
+ // Parse the JSON response as a string
+ try {
+ // Grab the object member that contains an array of all reponses...
+ // ...eval is necessary here since aSchema[0] is of unknown depth
+ jsonList = eval("(oResponse." + aSchema[0]+")");
+ }
+ catch(e) {
+ bError = true;
+ }
+
+ if(!jsonList) {
+ bError = true;
+ jsonList = [];
+ }
+
+ else if(!YAHOO.lang.isArray(jsonList)) {
+ jsonList = [jsonList];
+ }
+
+ // Loop through the array of all responses...
+ for(var i = jsonList.length-1; i >= 0 ; i--) {
+ var aResultItem = [];
+ var jsonResult = jsonList[i];
+ // ...and loop through each data field value of each response
+ for(var j = aSchema.length-1; j >= 1 ; j--) {
+ // ...and capture data into an array mapped according to the schema...
+ var dataFieldValue = jsonResult[aSchema[j]];
+ if(!dataFieldValue) {
+ dataFieldValue = "";
+ }
+ //YAHOO.log("data: " + i + " value:" +j+" = "+dataFieldValue,"debug",this.toString());
+ aResultItem.unshift(dataFieldValue);
+ }
+ // If schema isn't well defined, pass along the entire result object
+ if(aResultItem.length == 1) {
+ aResultItem.push(jsonResult);
+ }
+ // Capture the array of data field values in an array of results
+ aResults.unshift(aResultItem);
+ }
+
+ if(bError) {
+ aResults = null;
+ }
+
+ if(aResults === null) {
+ this.dataErrorEvent.fire(this, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATAPARSE);
+ YAHOO.log(YAHOO.widget.DataSource.ERROR_DATAPARSE, "error", this.toString());
+ aResults = [];
+ }
+ else {
+ var resultObj = {};
+ resultObj.query = decodeURIComponent(sQuery);
+ resultObj.results = aResults;
+ this._addCacheElem(resultObj);
+
+ this.getResultsEvent.fire(this, oParent, sQuery, aResults);
+ YAHOO.log("Results returned for query \"" + sQuery + "\": " +
+ YAHOO.lang.dump(aResults), "info", this.toString());
+ }
+
+ oCallbackFn(sQuery, aResults, oParent);
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Any success/failure response should decrement counter.
+ *
+ * @method _bumpPendingDown
+ * @private
+ */
+YAHOO.widget.DS_ScriptNode._bumpPendingDown = function() {
+ YAHOO.widget.DS_ScriptNode._nPending--;
+};
+
+
+/****************************************************************************/
+/****************************************************************************/
+/****************************************************************************/
+
+/**
+ * Implementation of YAHOO.widget.DataSource using a native Javascript function as
+ * its live data source.
+ *
+ * @class DS_JSFunction
+ * @constructor
+ * @extends YAHOO.widget.DataSource
+ * @param oFunction {HTMLFunction} In-memory Javascript function that returns query results as an array of objects.
+ * @param oConfigs {Object} (optional) Object literal of config params.
+ */
+YAHOO.widget.DS_JSFunction = function(oFunction, oConfigs) {
+ // Set any config params passed in to override defaults
+ if(oConfigs && (oConfigs.constructor == Object)) {
+ for(var sConfig in oConfigs) {
+ this[sConfig] = oConfigs[sConfig];
+ }
+ }
+
+ // Initialization sequence
+ if(!YAHOO.lang.isFunction(oFunction)) {
+ YAHOO.log("Could not instantiate JSFunction DataSource due to invalid arguments", "error", this.toString());
+ return;
+ }
+ else {
+ this.dataFunction = oFunction;
+ this._init();
+ YAHOO.log("JS Function DataSource initialized","info",this.toString());
+ }
+};
+
+YAHOO.widget.DS_JSFunction.prototype = new YAHOO.widget.DataSource();
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * In-memory Javascript function that returns query results.
+ *
+ * @property dataFunction
+ * @type HTMLFunction
+ */
+YAHOO.widget.DS_JSFunction.prototype.dataFunction = null;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Queries the live data source defined by function for results. Results are
+ * passed back to a callback function.
+ *
+ * @method doQuery
+ * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
+ * @param sQuery {String} Query string.
+ * @param oParent {Object} The object instance that has requested data.
+ */
+YAHOO.widget.DS_JSFunction.prototype.doQuery = function(oCallbackFn, sQuery, oParent) {
+ var oFunction = this.dataFunction;
+ var aResults = [];
+
+ aResults = oFunction(sQuery);
+ if(aResults === null) {
+ this.dataErrorEvent.fire(this, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATANULL);
+ YAHOO.log(YAHOO.widget.DataSource.ERROR_DATANULL, "error", this.toString());
+ return;
+ }
+
+ var resultObj = {};
+ resultObj.query = decodeURIComponent(sQuery);
+ resultObj.results = aResults;
+ this._addCacheElem(resultObj);
+
+ this.getResultsEvent.fire(this, oParent, sQuery, aResults);
+ YAHOO.log("Results returned for query \"" + sQuery +
+ "\": " + YAHOO.lang.dump(aResults), "info", this.toString());
+ oCallbackFn(sQuery, aResults, oParent);
+ return;
+};
+
+
+/****************************************************************************/
+/****************************************************************************/
+/****************************************************************************/
+
+/**
+ * Implementation of YAHOO.widget.DataSource using a native Javascript array as
+ * its live data source.
+ *
+ * @class DS_JSArray
+ * @constructor
+ * @extends YAHOO.widget.DataSource
+ * @param aData {String[]} In-memory Javascript array of simple string data.
+ * @param oConfigs {Object} (optional) Object literal of config params.
+ */
+YAHOO.widget.DS_JSArray = function(aData, oConfigs) {
+ // Set any config params passed in to override defaults
+ if(oConfigs && (oConfigs.constructor == Object)) {
+ for(var sConfig in oConfigs) {
+ this[sConfig] = oConfigs[sConfig];
+ }
+ }
+
+ // Initialization sequence
+ if(!YAHOO.lang.isArray(aData)) {
+ YAHOO.log("Could not instantiate JSArray DataSource due to invalid arguments", "error", this.toString());
+ return;
+ }
+ else {
+ this.data = aData;
+ this._init();
+ YAHOO.log("JS Array DataSource initialized","info",this.toString());
+ }
+};
+
+YAHOO.widget.DS_JSArray.prototype = new YAHOO.widget.DataSource();
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * In-memory Javascript array of strings.
+ *
+ * @property data
+ * @type Array
+ */
+YAHOO.widget.DS_JSArray.prototype.data = null;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Queries the live data source defined by data for results. Results are passed
+ * back to a callback function.
+ *
+ * @method doQuery
+ * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
+ * @param sQuery {String} Query string.
+ * @param oParent {Object} The object instance that has requested data.
+ */
+YAHOO.widget.DS_JSArray.prototype.doQuery = function(oCallbackFn, sQuery, oParent) {
+ var i;
+ var aData = this.data; // the array
+ var aResults = []; // container for results
+ var bMatchFound = false;
+ var bMatchContains = this.queryMatchContains;
+ if(sQuery) {
+ if(!this.queryMatchCase) {
+ sQuery = sQuery.toLowerCase();
+ }
+
+ // Loop through each element of the array...
+ // which can be a string or an array of strings
+ for(i = aData.length-1; i >= 0; i--) {
+ var aDataset = [];
+
+ if(YAHOO.lang.isString(aData[i])) {
+ aDataset[0] = aData[i];
+ }
+ else if(YAHOO.lang.isArray(aData[i])) {
+ aDataset = aData[i];
+ }
+
+ if(YAHOO.lang.isString(aDataset[0])) {
+ var sKeyIndex = (this.queryMatchCase) ?
+ encodeURIComponent(aDataset[0]).indexOf(sQuery):
+ encodeURIComponent(aDataset[0]).toLowerCase().indexOf(sQuery);
+
+ // A STARTSWITH match is when the query is found at the beginning of the key string...
+ if((!bMatchContains && (sKeyIndex === 0)) ||
+ // A CONTAINS match is when the query is found anywhere within the key string...
+ (bMatchContains && (sKeyIndex > -1))) {
+ // Stash a match into aResults[].
+ aResults.unshift(aDataset);
+ }
+ }
+ }
+ }
+ else {
+ for(i = aData.length-1; i >= 0; i--) {
+ if(YAHOO.lang.isString(aData[i])) {
+ aResults.unshift([aData[i]]);
+ }
+ else if(YAHOO.lang.isArray(aData[i])) {
+ aResults.unshift(aData[i]);
+ }
+ }
+ }
+
+ this.getResultsEvent.fire(this, oParent, sQuery, aResults);
+ YAHOO.log("Results returned for query \"" + sQuery +
+ "\": " + YAHOO.lang.dump(aResults), "info", this.toString());
+ oCallbackFn(sQuery, aResults, oParent);
+};
+
+YAHOO.register("autocomplete", YAHOO.widget.AutoComplete, {version: "2.5.2", build: "1076"});
diff --git a/lib/yui/autocomplete/autocomplete-min.js b/lib/yui/autocomplete/autocomplete-min.js
new file mode 100755
index 0000000000..2826de7cef
--- /dev/null
+++ b/lib/yui/autocomplete/autocomplete-min.js
@@ -0,0 +1,12 @@
+/*
+Copyright (c) 2008, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 2.5.2
+*/
+YAHOO.widget.AutoComplete=function(G,B,J,C){if(G&&B&&J){if(J instanceof YAHOO.widget.DataSource){this.dataSource=J;}else{return ;}if(YAHOO.util.Dom.inDocument(G)){if(YAHOO.lang.isString(G)){this._sName="instance"+YAHOO.widget.AutoComplete._nIndex+" "+G;this._elTextbox=document.getElementById(G);}else{this._sName=(G.id)?"instance"+YAHOO.widget.AutoComplete._nIndex+" "+G.id:"instance"+YAHOO.widget.AutoComplete._nIndex;this._elTextbox=G;}YAHOO.util.Dom.addClass(this._elTextbox,"yui-ac-input");}else{return ;}if(YAHOO.util.Dom.inDocument(B)){if(YAHOO.lang.isString(B)){this._elContainer=document.getElementById(B);}else{this._elContainer=B;}if(this._elContainer.style.display=="none"){}var D=this._elContainer.parentNode;var A=D.tagName.toLowerCase();if(A=="div"){YAHOO.util.Dom.addClass(D,"yui-ac");}else{}}else{return ;}if(C&&(C.constructor==Object)){for(var I in C){if(I){this[I]=C[I];}}}this._initContainer();this._initProps();this._initList();this._initContainerHelpers();var H=this;var F=this._elTextbox;var E=this._elContent;YAHOO.util.Event.addListener(F,"keyup",H._onTextboxKeyUp,H);YAHOO.util.Event.addListener(F,"keydown",H._onTextboxKeyDown,H);YAHOO.util.Event.addListener(F,"focus",H._onTextboxFocus,H);YAHOO.util.Event.addListener(F,"blur",H._onTextboxBlur,H);YAHOO.util.Event.addListener(E,"mouseover",H._onContainerMouseover,H);YAHOO.util.Event.addListener(E,"mouseout",H._onContainerMouseout,H);YAHOO.util.Event.addListener(E,"scroll",H._onContainerScroll,H);YAHOO.util.Event.addListener(E,"resize",H._onContainerResize,H);YAHOO.util.Event.addListener(F,"keypress",H._onTextboxKeyPress,H);YAHOO.util.Event.addListener(window,"unload",H._onWindowUnload,H);this.textboxFocusEvent=new YAHOO.util.CustomEvent("textboxFocus",this);this.textboxKeyEvent=new YAHOO.util.CustomEvent("textboxKey",this);this.dataRequestEvent=new YAHOO.util.CustomEvent("dataRequest",this);this.dataReturnEvent=new YAHOO.util.CustomEvent("dataReturn",this);this.dataErrorEvent=new YAHOO.util.CustomEvent("dataError",this);this.containerExpandEvent=new YAHOO.util.CustomEvent("containerExpand",this);this.typeAheadEvent=new YAHOO.util.CustomEvent("typeAhead",this);this.itemMouseOverEvent=new YAHOO.util.CustomEvent("itemMouseOver",this);this.itemMouseOutEvent=new YAHOO.util.CustomEvent("itemMouseOut",this);this.itemArrowToEvent=new YAHOO.util.CustomEvent("itemArrowTo",this);this.itemArrowFromEvent=new YAHOO.util.CustomEvent("itemArrowFrom",this);this.itemSelectEvent=new YAHOO.util.CustomEvent("itemSelect",this);this.unmatchedItemSelectEvent=new YAHOO.util.CustomEvent("unmatchedItemSelect",this);this.selectionEnforceEvent=new YAHOO.util.CustomEvent("selectionEnforce",this);this.containerCollapseEvent=new YAHOO.util.CustomEvent("containerCollapse",this);this.textboxBlurEvent=new YAHOO.util.CustomEvent("textboxBlur",this);F.setAttribute("autocomplete","off");YAHOO.widget.AutoComplete._nIndex++;}else{}};YAHOO.widget.AutoComplete.prototype.dataSource=null;YAHOO.widget.AutoComplete.prototype.minQueryLength=1;YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed=10;YAHOO.widget.AutoComplete.prototype.queryDelay=0.2;YAHOO.widget.AutoComplete.prototype.highlightClassName="yui-ac-highlight";YAHOO.widget.AutoComplete.prototype.prehighlightClassName=null;YAHOO.widget.AutoComplete.prototype.delimChar=null;YAHOO.widget.AutoComplete.prototype.autoHighlight=true;YAHOO.widget.AutoComplete.prototype.typeAhead=false;YAHOO.widget.AutoComplete.prototype.animHoriz=false;YAHOO.widget.AutoComplete.prototype.animVert=true;YAHOO.widget.AutoComplete.prototype.animSpeed=0.3;YAHOO.widget.AutoComplete.prototype.forceSelection=false;YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete=true;YAHOO.widget.AutoComplete.prototype.alwaysShowContainer=false;YAHOO.widget.AutoComplete.prototype.useIFrame=false;YAHOO.widget.AutoComplete.prototype.useShadow=false;YAHOO.widget.AutoComplete.prototype.toString=function(){return"AutoComplete "+this._sName;};YAHOO.widget.AutoComplete.prototype.isContainerOpen=function(){return this._bContainerOpen;};YAHOO.widget.AutoComplete.prototype.getListItems=function(){return this._aListItems;};YAHOO.widget.AutoComplete.prototype.getListItemData=function(A){if(A._oResultData){return A._oResultData;}else{return false;}};YAHOO.widget.AutoComplete.prototype.setHeader=function(B){if(this._elHeader){var A=this._elHeader;if(B){A.innerHTML=B;A.style.display="block";}else{A.innerHTML="";A.style.display="none";}}};YAHOO.widget.AutoComplete.prototype.setFooter=function(B){if(this._elFooter){var A=this._elFooter;if(B){A.innerHTML=B;A.style.display="block";}else{A.innerHTML="";A.style.display="none";}}};YAHOO.widget.AutoComplete.prototype.setBody=function(A){if(this._elBody){var B=this._elBody;if(A){B.innerHTML=A;B.style.display="block";B.style.display="block";}else{B.innerHTML="";B.style.display="none";}this._maxResultsDisplayed=0;}};YAHOO.widget.AutoComplete.prototype.formatResult=function(B,C){var A=B[0];if(A){return A;}else{return"";}};YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer=function(D,A,C,B){return true;};YAHOO.widget.AutoComplete.prototype.sendQuery=function(A){this._sendQuery(A);};YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery=function(A){return A;};YAHOO.widget.AutoComplete.prototype.destroy=function(){var B=this.toString();var A=this._elTextbox;var D=this._elContainer;this.textboxFocusEvent.unsubscribeAll();this.textboxKeyEvent.unsubscribeAll();this.dataRequestEvent.unsubscribeAll();this.dataReturnEvent.unsubscribeAll();this.dataErrorEvent.unsubscribeAll();this.containerExpandEvent.unsubscribeAll();this.typeAheadEvent.unsubscribeAll();this.itemMouseOverEvent.unsubscribeAll();this.itemMouseOutEvent.unsubscribeAll();this.itemArrowToEvent.unsubscribeAll();this.itemArrowFromEvent.unsubscribeAll();this.itemSelectEvent.unsubscribeAll();this.unmatchedItemSelectEvent.unsubscribeAll();this.selectionEnforceEvent.unsubscribeAll();this.containerCollapseEvent.unsubscribeAll();this.textboxBlurEvent.unsubscribeAll();YAHOO.util.Event.purgeElement(A,true);
+YAHOO.util.Event.purgeElement(D,true);D.innerHTML="";for(var C in this){if(YAHOO.lang.hasOwnProperty(this,C)){this[C]=null;}}};YAHOO.widget.AutoComplete.prototype.textboxFocusEvent=null;YAHOO.widget.AutoComplete.prototype.textboxKeyEvent=null;YAHOO.widget.AutoComplete.prototype.dataRequestEvent=null;YAHOO.widget.AutoComplete.prototype.dataReturnEvent=null;YAHOO.widget.AutoComplete.prototype.dataErrorEvent=null;YAHOO.widget.AutoComplete.prototype.containerExpandEvent=null;YAHOO.widget.AutoComplete.prototype.typeAheadEvent=null;YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent=null;YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent=null;YAHOO.widget.AutoComplete.prototype.itemArrowToEvent=null;YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent=null;YAHOO.widget.AutoComplete.prototype.itemSelectEvent=null;YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent=null;YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent=null;YAHOO.widget.AutoComplete.prototype.containerCollapseEvent=null;YAHOO.widget.AutoComplete.prototype.textboxBlurEvent=null;YAHOO.widget.AutoComplete._nIndex=0;YAHOO.widget.AutoComplete.prototype._sName=null;YAHOO.widget.AutoComplete.prototype._elTextbox=null;YAHOO.widget.AutoComplete.prototype._elContainer=null;YAHOO.widget.AutoComplete.prototype._elContent=null;YAHOO.widget.AutoComplete.prototype._elHeader=null;YAHOO.widget.AutoComplete.prototype._elBody=null;YAHOO.widget.AutoComplete.prototype._elFooter=null;YAHOO.widget.AutoComplete.prototype._elShadow=null;YAHOO.widget.AutoComplete.prototype._elIFrame=null;YAHOO.widget.AutoComplete.prototype._bFocused=true;YAHOO.widget.AutoComplete.prototype._oAnim=null;YAHOO.widget.AutoComplete.prototype._bContainerOpen=false;YAHOO.widget.AutoComplete.prototype._bOverContainer=false;YAHOO.widget.AutoComplete.prototype._aListItems=null;YAHOO.widget.AutoComplete.prototype._nDisplayedItems=0;YAHOO.widget.AutoComplete.prototype._maxResultsDisplayed=0;YAHOO.widget.AutoComplete.prototype._sCurQuery=null;YAHOO.widget.AutoComplete.prototype._sSavedQuery=null;YAHOO.widget.AutoComplete.prototype._oCurItem=null;YAHOO.widget.AutoComplete.prototype._bItemSelected=false;YAHOO.widget.AutoComplete.prototype._nKeyCode=null;YAHOO.widget.AutoComplete.prototype._nDelayID=-1;YAHOO.widget.AutoComplete.prototype._iFrameSrc="javascript:false;";YAHOO.widget.AutoComplete.prototype._queryInterval=null;YAHOO.widget.AutoComplete.prototype._sLastTextboxValue=null;YAHOO.widget.AutoComplete.prototype._initProps=function(){var B=this.minQueryLength;if(!YAHOO.lang.isNumber(B)){this.minQueryLength=1;}var D=this.maxResultsDisplayed;if(!YAHOO.lang.isNumber(D)||(D<1)){this.maxResultsDisplayed=10;}var E=this.queryDelay;if(!YAHOO.lang.isNumber(E)||(E<0)){this.queryDelay=0.2;}var A=this.delimChar;if(YAHOO.lang.isString(A)&&(A.length>0)){this.delimChar=[A];}else{if(!YAHOO.lang.isArray(A)){this.delimChar=null;}}var C=this.animSpeed;if((this.animHoriz||this.animVert)&&YAHOO.util.Anim){if(!YAHOO.lang.isNumber(C)||(C<0)){this.animSpeed=0.3;}if(!this._oAnim){this._oAnim=new YAHOO.util.Anim(this._elContent,{},this.animSpeed);}else{this._oAnim.duration=this.animSpeed;}}if(this.forceSelection&&A){}};YAHOO.widget.AutoComplete.prototype._initContainerHelpers=function(){if(this.useShadow&&!this._elShadow){var A=document.createElement("div");A.className="yui-ac-shadow";this._elShadow=this._elContainer.appendChild(A);}if(this.useIFrame&&!this._elIFrame){var B=document.createElement("iframe");B.src=this._iFrameSrc;B.frameBorder=0;B.scrolling="no";B.style.position="absolute";B.style.width="100%";B.style.height="100%";B.tabIndex=-1;this._elIFrame=this._elContainer.appendChild(B);}};YAHOO.widget.AutoComplete.prototype._initContainer=function(){YAHOO.util.Dom.addClass(this._elContainer,"yui-ac-container");if(!this._elContent){var C=document.createElement("div");C.className="yui-ac-content";C.style.display="none";this._elContent=this._elContainer.appendChild(C);var B=document.createElement("div");B.className="yui-ac-hd";B.style.display="none";this._elHeader=this._elContent.appendChild(B);var D=document.createElement("div");D.className="yui-ac-bd";this._elBody=this._elContent.appendChild(D);var A=document.createElement("div");A.className="yui-ac-ft";A.style.display="none";this._elFooter=this._elContent.appendChild(A);}else{}};YAHOO.widget.AutoComplete.prototype._initList=function(){this._aListItems=[];while(this._elBody.hasChildNodes()){var B=this.getListItems();if(B){for(var A=B.length-1;A>=0;A--){B[A]=null;}}this._elBody.innerHTML="";}var E=document.createElement("ul");E=this._elBody.appendChild(E);for(var C=0;C=18&&A<=20)||(A==27)||(A>=33&&A<=35)||(A>=36&&A<=40)||(A>=44&&A<=45)){return true;}return false;};YAHOO.widget.AutoComplete.prototype._sendQuery=function(G){if(this.minQueryLength==-1){this._toggleContainer(false);return ;}var C=(this.delimChar)?this.delimChar:null;if(C){var E=-1;for(var B=C.length-1;B>=0;B--){var F=G.lastIndexOf(C[B]);if(F>E){E=F;
+}}if(C[B]==" "){for(var A=C.length-1;A>=0;A--){if(G[E-1]==C[A]){E--;break;}}}if(E>-1){var D=E+1;while(G.charAt(D)==" "){D+=1;}this._sSavedQuery=G.substring(0,D);G=G.substr(D);}else{if(G.indexOf(this._sSavedQuery)<0){this._sSavedQuery=null;}}}if((G&&(G.length0)){if(this._nDelayID!=-1){clearTimeout(this._nDelayID);}this._toggleContainer(false);return ;}G=encodeURIComponent(G);this._nDelayID=-1;G=this.doBeforeSendQuery(G);this.dataRequestEvent.fire(this,G);this.dataSource.getResults(this._populateList,G,this);};YAHOO.widget.AutoComplete.prototype._populateList=function(K,L,I){if(L===null){I.dataErrorEvent.fire(I,K);}if(!I._bFocused||!L){return ;}var A=(navigator.userAgent.toLowerCase().indexOf("opera")!=-1);var O=I._elContent.style;O.width=(!A)?null:"";O.height=(!A)?null:"";var H=decodeURIComponent(K);I._sCurQuery=H;I._bItemSelected=false;if(I._maxResultsDisplayed!=I.maxResultsDisplayed){I._initList();}var C=Math.min(L.length,I.maxResultsDisplayed);I._nDisplayedItems=C;if(C>0){I._initContainerHelpers();var D=I._aListItems;for(var G=C-1;G>=0;G--){var N=D[G];var B=L[G];N.innerHTML=I.formatResult(B,H);N.style.display="list-item";N._sResultKey=B[0];N._oResultData=B;}for(var F=D.length-1;F>=C;F--){var M=D[F];M.innerHTML=null;M.style.display="none";M._sResultKey=null;M._oResultData=null;}var J=I.doBeforeExpandContainer(I._elTextbox,I._elContainer,K,L);I._toggleContainer(J);if(I.autoHighlight){var E=D[0];I._toggleHighlight(E,"to");I.itemArrowToEvent.fire(I,E);I._typeAhead(E,K);}else{I._oCurItem=null;}}else{I._toggleContainer(false);}I.dataReturnEvent.fire(I,K,L);};YAHOO.widget.AutoComplete.prototype._clearSelection=function(){var C=this._elTextbox.value;var B=(this.delimChar)?this.delimChar[0]:null;var A=(B)?C.lastIndexOf(B,C.length-2):-1;if(A>-1){this._elTextbox.value=C.substring(0,A);}else{this._elTextbox.value="";}this._sSavedQuery=this._elTextbox.value;this.selectionEnforceEvent.fire(this);};YAHOO.widget.AutoComplete.prototype._textMatchesOption=function(){var D=null;for(var A=this._nDisplayedItems-1;A>=0;A--){var C=this._aListItems[A];var B=C._sResultKey.toLowerCase();if(B==this._sCurQuery.toLowerCase()){D=C;break;}}return(D);};YAHOO.widget.AutoComplete.prototype._typeAhead=function(D,G){if(!this.typeAhead||(this._nKeyCode==8)){return ;}var F=this._elTextbox;var E=this._elTextbox.value;if(!F.setSelectionRange&&!F.createTextRange){return ;}var B=E.length;this._updateValue(D);var C=F.value.length;this._selectText(F,B,C);var A=F.value.substr(B,C);this.typeAheadEvent.fire(this,G,A);};YAHOO.widget.AutoComplete.prototype._selectText=function(D,A,B){if(D.setSelectionRange){D.setSelectionRange(A,B);}else{if(D.createTextRange){var C=D.createTextRange();C.moveStart("character",A);C.moveEnd("character",B-D.value.length);C.select();}else{D.select();}}};YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers=function(B){var D=false;var C=this._elContent.offsetWidth+"px";var A=this._elContent.offsetHeight+"px";if(this.useIFrame&&this._elIFrame){D=true;if(B){this._elIFrame.style.width=C;this._elIFrame.style.height=A;}else{this._elIFrame.style.width=0;this._elIFrame.style.height=0;}}if(this.useShadow&&this._elShadow){D=true;if(B){this._elShadow.style.width=C;this._elShadow.style.height=A;}else{this._elShadow.style.width=0;this._elShadow.style.height=0;}}};YAHOO.widget.AutoComplete.prototype._toggleContainer=function(K){var E=this._elContainer;if(this.alwaysShowContainer&&this._bContainerOpen){return ;}if(!K){this._elContent.scrollTop=0;var C=this._aListItems;if(C&&(C.length>0)){for(var H=C.length-1;H>=0;H--){C[H].style.display="none";}}if(this._oCurItem){this._toggleHighlight(this._oCurItem,"from");}this._oCurItem=null;this._nDisplayedItems=0;this._sCurQuery=null;}if(!K&&!this._bContainerOpen){this._elContent.style.display="none";return ;}var B=this._oAnim;if(B&&B.getEl()&&(this.animHoriz||this.animVert)){if(!K){this._toggleContainerHelpers(K);}if(B.isAnimated()){B.stop();}var I=this._elContent.cloneNode(true);E.appendChild(I);I.style.top="-9000px";I.style.display="block";var G=I.offsetWidth;var D=I.offsetHeight;var A=(this.animHoriz)?0:G;var F=(this.animVert)?0:D;B.attributes=(K)?{width:{to:G},height:{to:D}}:{width:{to:A},height:{to:F}};if(K&&!this._bContainerOpen){this._elContent.style.width=A+"px";this._elContent.style.height=F+"px";}else{this._elContent.style.width=G+"px";this._elContent.style.height=D+"px";}E.removeChild(I);I=null;var J=this;var L=function(){B.onComplete.unsubscribeAll();if(K){J.containerExpandEvent.fire(J);}else{J._elContent.style.display="none";J.containerCollapseEvent.fire(J);}J._toggleContainerHelpers(K);};this._elContent.style.display="block";B.onComplete.subscribe(L);B.animate();this._bContainerOpen=K;}else{if(K){this._elContent.style.display="block";this.containerExpandEvent.fire(this);}else{this._elContent.style.display="none";this.containerCollapseEvent.fire(this);}this._toggleContainerHelpers(K);this._bContainerOpen=K;}};YAHOO.widget.AutoComplete.prototype._toggleHighlight=function(A,C){var B=this.highlightClassName;if(this._oCurItem){YAHOO.util.Dom.removeClass(this._oCurItem,B);}if((C=="to")&&B){YAHOO.util.Dom.addClass(A,B);this._oCurItem=A;}};YAHOO.widget.AutoComplete.prototype._togglePrehighlight=function(A,C){if(A==this._oCurItem){return ;}var B=this.prehighlightClassName;if((C=="mouseover")&&B){YAHOO.util.Dom.addClass(A,B);}else{YAHOO.util.Dom.removeClass(A,B);}};YAHOO.widget.AutoComplete.prototype._updateValue=function(E){var F=this._elTextbox;var D=(this.delimChar)?(this.delimChar[0]||this.delimChar):null;var B=this._sSavedQuery;var C=E._sResultKey;F.focus();F.value="";if(D){if(B){F.value=B;}F.value+=C+D;if(D!=" "){F.value+=" ";}}else{F.value=C;}if(F.type=="textarea"){F.scrollTop=F.scrollHeight;}var A=F.value.length;this._selectText(F,A,A);this._oCurItem=E;};YAHOO.widget.AutoComplete.prototype._selectItem=function(A){this._bItemSelected=true;this._updateValue(A);this._cancelIntervalDetection(this);this.itemSelectEvent.fire(this,A,A._oResultData);
+this._toggleContainer(false);};YAHOO.widget.AutoComplete.prototype._jumpSelection=function(){if(this._oCurItem){this._selectItem(this._oCurItem);}else{this._toggleContainer(false);}};YAHOO.widget.AutoComplete.prototype._moveSelection=function(G){if(this._bContainerOpen){var E=this._oCurItem;var F=-1;if(E){F=E._nItemIndex;}var D=(G==40)?(F+1):(F-1);if(D<-2||D>=this._nDisplayedItems){return ;}if(E){this._toggleHighlight(E,"from");this.itemArrowFromEvent.fire(this,E);}if(D==-1){if(this.delimChar&&this._sSavedQuery){if(!this._textMatchesOption()){this._elTextbox.value=this._sSavedQuery;}else{this._elTextbox.value=this._sSavedQuery+this._sCurQuery;}}else{this._elTextbox.value=this._sCurQuery;}this._oCurItem=null;return ;}if(D==-2){this._toggleContainer(false);return ;}var C=this._aListItems[D];var A=this._elContent;var B=((YAHOO.util.Dom.getStyle(A,"overflow")=="auto")||(YAHOO.util.Dom.getStyle(A,"overflowY")=="auto"));if(B&&(D>-1)&&(D(A.scrollTop+A.offsetHeight)){A.scrollTop=(C.offsetTop+C.offsetHeight)-A.offsetHeight;}else{if((C.offsetTop+C.offsetHeight)(A.scrollTop+A.offsetHeight)){this._elContent.scrollTop=(C.offsetTop+C.offsetHeight)-A.offsetHeight;}}}}this._toggleHighlight(C,"to");this.itemArrowToEvent.fire(this,C);if(this.typeAhead){this._updateValue(C);}}};YAHOO.widget.AutoComplete.prototype._onItemMouseover=function(A,B){if(B.prehighlightClassName){B._togglePrehighlight(this,"mouseover");}else{B._toggleHighlight(this,"to");}B.itemMouseOverEvent.fire(B,this);};YAHOO.widget.AutoComplete.prototype._onItemMouseout=function(A,B){if(B.prehighlightClassName){B._togglePrehighlight(this,"mouseout");}else{B._toggleHighlight(this,"from");}B.itemMouseOutEvent.fire(B,this);};YAHOO.widget.AutoComplete.prototype._onItemMouseclick=function(A,B){B._toggleHighlight(this,"to");B._selectItem(this);};YAHOO.widget.AutoComplete.prototype._onContainerMouseover=function(A,B){B._bOverContainer=true;};YAHOO.widget.AutoComplete.prototype._onContainerMouseout=function(A,B){B._bOverContainer=false;if(B._oCurItem){B._toggleHighlight(B._oCurItem,"to");}};YAHOO.widget.AutoComplete.prototype._onContainerScroll=function(A,B){B._elTextbox.focus();};YAHOO.widget.AutoComplete.prototype._onContainerResize=function(A,B){B._toggleContainerHelpers(B._bContainerOpen);};YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown=function(A,B){var C=A.keyCode;switch(C){case 9:if((navigator.userAgent.toLowerCase().indexOf("mac")==-1)){if(B._oCurItem){if(B.delimChar&&(B._nKeyCode!=C)){if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);}}B._selectItem(B._oCurItem);}else{B._toggleContainer(false);}}break;case 13:if((navigator.userAgent.toLowerCase().indexOf("mac")==-1)){if(B._oCurItem){if(B._nKeyCode!=C){if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);}}B._selectItem(B._oCurItem);}else{B._toggleContainer(false);}}break;case 27:B._toggleContainer(false);return ;case 39:B._jumpSelection();break;case 38:YAHOO.util.Event.stopEvent(A);B._moveSelection(C);break;case 40:YAHOO.util.Event.stopEvent(A);B._moveSelection(C);break;default:break;}};YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress=function(A,B){var C=A.keyCode;if((navigator.userAgent.toLowerCase().indexOf("mac")!=-1)){switch(C){case 9:if(B._oCurItem){if(B.delimChar&&(B._nKeyCode!=C)){if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);}}B._selectItem(B._oCurItem);}else{B._toggleContainer(false);}break;case 13:if(B._oCurItem){if(B._nKeyCode!=C){if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);}}B._selectItem(B._oCurItem);}else{B._toggleContainer(false);}break;default:break;}}else{if(C==229){B._queryInterval=setInterval(function(){B._onIMEDetected(B);},500);}}};YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp=function(B,D){D._initProps();var E=B.keyCode;D._nKeyCode=E;var C=this.value;if(D._isIgnoreKey(E)||(C.toLowerCase()==D._sCurQuery)){return ;}else{D._bItemSelected=false;YAHOO.util.Dom.removeClass(D._oCurItem,D.highlightClassName);D._oCurItem=null;D.textboxKeyEvent.fire(D,E);}if(D.queryDelay>0){var A=setTimeout(function(){D._sendQuery(C);},(D.queryDelay*1000));if(D._nDelayID!=-1){clearTimeout(D._nDelayID);}D._nDelayID=A;}else{D._sendQuery(C);}};YAHOO.widget.AutoComplete.prototype._onTextboxFocus=function(A,B){B._elTextbox.setAttribute("autocomplete","off");B._bFocused=true;if(!B._bItemSelected){B.textboxFocusEvent.fire(B);}};YAHOO.widget.AutoComplete.prototype._onTextboxBlur=function(A,B){if(!B._bOverContainer||(B._nKeyCode==9)){if(!B._bItemSelected){var C=B._textMatchesOption();if(!B._bContainerOpen||(B._bContainerOpen&&(C===null))){if(B.forceSelection){B._clearSelection();}else{B.unmatchedItemSelectEvent.fire(B);}}else{if(B.forceSelection){B._selectItem(C);}}}if(B._bContainerOpen){B._toggleContainer(false);}B._cancelIntervalDetection(B);B._bFocused=false;B.textboxBlurEvent.fire(B);}};YAHOO.widget.AutoComplete.prototype._onWindowUnload=function(A,B){if(B&&B._elTextbox&&B.allowBrowserAutocomplete){B._elTextbox.setAttribute("autocomplete","on");}};YAHOO.widget.DataSource=function(){};YAHOO.widget.DataSource.ERROR_DATANULL="Response data was null";YAHOO.widget.DataSource.ERROR_DATAPARSE="Response data could not be parsed";YAHOO.widget.DataSource.prototype.maxCacheEntries=15;YAHOO.widget.DataSource.prototype.queryMatchContains=false;YAHOO.widget.DataSource.prototype.queryMatchSubset=false;YAHOO.widget.DataSource.prototype.queryMatchCase=false;YAHOO.widget.DataSource.prototype.toString=function(){return"DataSource "+this._sName;};YAHOO.widget.DataSource.prototype.getResults=function(A,D,B){var C=this._doQueryCache(A,D,B);if(C.length===0){this.queryEvent.fire(this,B,D);this.doQuery(A,D,B);}};YAHOO.widget.DataSource.prototype.doQuery=function(A,C,B){};YAHOO.widget.DataSource.prototype.flushCache=function(){if(this._aCache){this._aCache=[];}if(this._aCacheHelper){this._aCacheHelper=[];
+}this.cacheFlushEvent.fire(this);};YAHOO.widget.DataSource.prototype.queryEvent=null;YAHOO.widget.DataSource.prototype.cacheQueryEvent=null;YAHOO.widget.DataSource.prototype.getResultsEvent=null;YAHOO.widget.DataSource.prototype.getCachedResultsEvent=null;YAHOO.widget.DataSource.prototype.dataErrorEvent=null;YAHOO.widget.DataSource.prototype.cacheFlushEvent=null;YAHOO.widget.DataSource._nIndex=0;YAHOO.widget.DataSource.prototype._sName=null;YAHOO.widget.DataSource.prototype._aCache=null;YAHOO.widget.DataSource.prototype._init=function(){var A=this.maxCacheEntries;if(!YAHOO.lang.isNumber(A)||(A<0)){A=0;}if(A>0&&!this._aCache){this._aCache=[];}this._sName="instance"+YAHOO.widget.DataSource._nIndex;YAHOO.widget.DataSource._nIndex++;this.queryEvent=new YAHOO.util.CustomEvent("query",this);this.cacheQueryEvent=new YAHOO.util.CustomEvent("cacheQuery",this);this.getResultsEvent=new YAHOO.util.CustomEvent("getResults",this);this.getCachedResultsEvent=new YAHOO.util.CustomEvent("getCachedResults",this);this.dataErrorEvent=new YAHOO.util.CustomEvent("dataError",this);this.cacheFlushEvent=new YAHOO.util.CustomEvent("cacheFlush",this);};YAHOO.widget.DataSource.prototype._addCacheElem=function(B){var A=this._aCache;if(!A||!B||!B.query||!B.results){return ;}if(A.length>=this.maxCacheEntries){A.shift();}A.push(B);};YAHOO.widget.DataSource.prototype._doQueryCache=function(A,I,N){var H=[];var G=false;var J=this._aCache;var F=(J)?J.length:0;var K=this.queryMatchContains;var D;if((this.maxCacheEntries>0)&&J&&(F>0)){this.cacheQueryEvent.fire(this,N,I);if(!this.queryMatchCase){D=I;I=I.toLowerCase();}for(var P=F-1;P>=0;P--){var E=J[P];var B=E.results;var C=(!this.queryMatchCase)?encodeURIComponent(E.query).toLowerCase():encodeURIComponent(E.query);if(C==I){G=true;H=B;if(P!=F-1){J.splice(P,1);this._addCacheElem(E);}break;}else{if(this.queryMatchSubset){for(var O=I.length-1;O>=0;O--){var R=I.substr(0,O);if(C==R){G=true;for(var M=B.length-1;M>=0;M--){var Q=B[M];var L=(this.queryMatchCase)?encodeURIComponent(Q[0]).indexOf(I):encodeURIComponent(Q[0]).toLowerCase().indexOf(I);if((!K&&(L===0))||(K&&(L>-1))){H.unshift(Q);}}E={};E.query=I;E.results=H;this._addCacheElem(E);break;}}if(G){break;}}}}if(G){this.getCachedResultsEvent.fire(this,N,D,H);A(D,H,N);}}return H;};YAHOO.widget.DS_XHR=function(C,A,D){if(D&&(D.constructor==Object)){for(var B in D){this[B]=D[B];}}if(!YAHOO.lang.isArray(A)||!YAHOO.lang.isString(C)){return ;}this.schema=A;this.scriptURI=C;this._init();};YAHOO.widget.DS_XHR.prototype=new YAHOO.widget.DataSource();YAHOO.widget.DS_XHR.TYPE_JSON=0;YAHOO.widget.DS_XHR.TYPE_XML=1;YAHOO.widget.DS_XHR.TYPE_FLAT=2;YAHOO.widget.DS_XHR.ERROR_DATAXHR="XHR response failed";YAHOO.widget.DS_XHR.prototype.connMgr=YAHOO.util.Connect;YAHOO.widget.DS_XHR.prototype.connTimeout=0;YAHOO.widget.DS_XHR.prototype.scriptURI=null;YAHOO.widget.DS_XHR.prototype.scriptQueryParam="query";YAHOO.widget.DS_XHR.prototype.scriptQueryAppend="";YAHOO.widget.DS_XHR.prototype.responseType=YAHOO.widget.DS_XHR.TYPE_JSON;YAHOO.widget.DS_XHR.prototype.responseStripAfter="\n0){D+="&"+this.scriptQueryAppend;}var C=null;var F=this;var I=function(K){if(!F._oConn||(K.tId!=F._oConn.tId)){F.dataErrorEvent.fire(F,B,G,YAHOO.widget.DataSource.ERROR_DATANULL);return ;}for(var N in K){}if(!J){K=K.responseText;}else{K=K.responseXML;}if(K===null){F.dataErrorEvent.fire(F,B,G,YAHOO.widget.DataSource.ERROR_DATANULL);return ;}var M=F.parseResponse(G,K,B);var L={};L.query=decodeURIComponent(G);L.results=M;if(M===null){F.dataErrorEvent.fire(F,B,G,YAHOO.widget.DataSource.ERROR_DATAPARSE);M=[];}else{F.getResultsEvent.fire(F,B,G,M);F._addCacheElem(L);}E(G,M,B);};var A=function(K){F.dataErrorEvent.fire(F,B,G,YAHOO.widget.DS_XHR.ERROR_DATAXHR);return ;};var H={success:I,failure:A};if(YAHOO.lang.isNumber(this.connTimeout)&&(this.connTimeout>0)){H.timeout=this.connTimeout;}if(this._oConn){this.connMgr.abort(this._oConn);}F._oConn=this.connMgr.asyncRequest("GET",D,H,null);};YAHOO.widget.DS_XHR.prototype.parseResponse=function(sQuery,oResponse,oParent){var aSchema=this.schema;var aResults=[];var bError=false;var nEnd=((this.responseStripAfter!=="")&&(oResponse.indexOf))?oResponse.indexOf(this.responseStripAfter):-1;if(nEnd!=-1){oResponse=oResponse.substring(0,nEnd);}switch(this.responseType){case YAHOO.widget.DS_XHR.TYPE_JSON:var jsonList,jsonObjParsed;if(YAHOO.lang.JSON){jsonObjParsed=YAHOO.lang.JSON.parse(oResponse);if(!jsonObjParsed){bError=true;break;}else{try{jsonList=eval("jsonObjParsed."+aSchema[0]);}catch(e){bError=true;break;}}}else{if(oResponse.parseJSON){jsonObjParsed=oResponse.parseJSON();if(!jsonObjParsed){bError=true;}else{try{jsonList=eval("jsonObjParsed."+aSchema[0]);}catch(e){bError=true;break;}}}else{if(window.JSON){jsonObjParsed=JSON.parse(oResponse);if(!jsonObjParsed){bError=true;break;}else{try{jsonList=eval("jsonObjParsed."+aSchema[0]);}catch(e){bError=true;break;}}}else{try{while(oResponse.substring(0,1)==" "){oResponse=oResponse.substring(1,oResponse.length);}if(oResponse.indexOf("{")<0){bError=true;break;}if(oResponse.indexOf("{}")===0){break;}var jsonObjRaw=eval("("+oResponse+")");if(!jsonObjRaw){bError=true;break;}jsonList=eval("(jsonObjRaw."+aSchema[0]+")");}catch(e){bError=true;break;}}}}if(!jsonList){bError=true;break;}if(!YAHOO.lang.isArray(jsonList)){jsonList=[jsonList];}for(var i=jsonList.length-1;i>=0;i--){var aResultItem=[];var jsonResult=jsonList[i];for(var j=aSchema.length-1;j>=1;j--){var dataFieldValue=jsonResult[aSchema[j]];if(!dataFieldValue){dataFieldValue="";}aResultItem.unshift(dataFieldValue);}if(aResultItem.length==1){aResultItem.push(jsonResult);}aResults.unshift(aResultItem);}break;case YAHOO.widget.DS_XHR.TYPE_XML:var xmlList=oResponse.getElementsByTagName(aSchema[0]);if(!xmlList){bError=true;break;}for(var k=xmlList.length-1;k>=0;k--){var result=xmlList.item(k);
+var aFieldSet=[];for(var m=aSchema.length-1;m>=1;m--){var sValue=null;var xmlAttr=result.attributes.getNamedItem(aSchema[m]);if(xmlAttr){sValue=xmlAttr.value;}else{var xmlNode=result.getElementsByTagName(aSchema[m]);if(xmlNode&&xmlNode.item(0)&&xmlNode.item(0).firstChild){sValue=xmlNode.item(0).firstChild.nodeValue;}else{sValue="";}}aFieldSet.unshift(sValue);}aResults.unshift(aFieldSet);}break;case YAHOO.widget.DS_XHR.TYPE_FLAT:if(oResponse.length>0){var newLength=oResponse.length-aSchema[0].length;if(oResponse.substr(newLength)==aSchema[0]){oResponse=oResponse.substr(0,newLength);}if(oResponse.length>0){var aRecords=oResponse.split(aSchema[0]);for(var n=aRecords.length-1;n>=0;n--){if(aRecords[n].length>0){aResults[n]=aRecords[n].split(aSchema[1]);}}}}break;default:break;}sQuery=null;oResponse=null;oParent=null;if(bError){return null;}else{return aResults;}};YAHOO.widget.DS_XHR.prototype._oConn=null;YAHOO.widget.DS_ScriptNode=function(D,A,C){if(C&&(C.constructor==Object)){for(var B in C){this[B]=C[B];}}if(!YAHOO.lang.isArray(A)||!YAHOO.lang.isString(D)){return ;}this.schema=A;this.scriptURI=D;this._init();};YAHOO.widget.DS_ScriptNode.prototype=new YAHOO.widget.DataSource();YAHOO.widget.DS_ScriptNode.prototype.getUtility=YAHOO.util.Get;YAHOO.widget.DS_ScriptNode.prototype.scriptURI=null;YAHOO.widget.DS_ScriptNode.prototype.scriptQueryParam="query";YAHOO.widget.DS_ScriptNode.prototype.asyncMode="allowAll";YAHOO.widget.DS_ScriptNode.prototype.scriptCallbackParam="callback";YAHOO.widget.DS_ScriptNode.callbacks=[];YAHOO.widget.DS_ScriptNode._nId=0;YAHOO.widget.DS_ScriptNode._nPending=0;YAHOO.widget.DS_ScriptNode.prototype.doQuery=function(A,F,C){var B=this;if(YAHOO.widget.DS_ScriptNode._nPending===0){YAHOO.widget.DS_ScriptNode.callbacks=[];YAHOO.widget.DS_ScriptNode._nId=0;}var E=YAHOO.widget.DS_ScriptNode._nId;YAHOO.widget.DS_ScriptNode._nId++;YAHOO.widget.DS_ScriptNode.callbacks[E]=function(G){if((B.asyncMode!=="ignoreStaleResponses")||(E===YAHOO.widget.DS_ScriptNode.callbacks.length-1)){B.handleResponse(G,A,F,C);}else{}delete YAHOO.widget.DS_ScriptNode.callbacks[E];};YAHOO.widget.DS_ScriptNode._nPending++;var D=this.scriptURI+"&"+this.scriptQueryParam+"="+F+"&"+this.scriptCallbackParam+"=YAHOO.widget.DS_ScriptNode.callbacks["+E+"]";this.getUtility.script(D,{autopurge:true,onsuccess:YAHOO.widget.DS_ScriptNode._bumpPendingDown,onfail:YAHOO.widget.DS_ScriptNode._bumpPendingDown});};YAHOO.widget.DS_ScriptNode.prototype.handleResponse=function(oResponse,oCallbackFn,sQuery,oParent){var aSchema=this.schema;var aResults=[];var bError=false;var jsonList,jsonObjParsed;try{jsonList=eval("(oResponse."+aSchema[0]+")");}catch(e){bError=true;}if(!jsonList){bError=true;jsonList=[];}else{if(!YAHOO.lang.isArray(jsonList)){jsonList=[jsonList];}}for(var i=jsonList.length-1;i>=0;i--){var aResultItem=[];var jsonResult=jsonList[i];for(var j=aSchema.length-1;j>=1;j--){var dataFieldValue=jsonResult[aSchema[j]];if(!dataFieldValue){dataFieldValue="";}aResultItem.unshift(dataFieldValue);}if(aResultItem.length==1){aResultItem.push(jsonResult);}aResults.unshift(aResultItem);}if(bError){aResults=null;}if(aResults===null){this.dataErrorEvent.fire(this,oParent,sQuery,YAHOO.widget.DataSource.ERROR_DATAPARSE);aResults=[];}else{var resultObj={};resultObj.query=decodeURIComponent(sQuery);resultObj.results=aResults;this._addCacheElem(resultObj);this.getResultsEvent.fire(this,oParent,sQuery,aResults);}oCallbackFn(sQuery,aResults,oParent);};YAHOO.widget.DS_ScriptNode._bumpPendingDown=function(){YAHOO.widget.DS_ScriptNode._nPending--;};YAHOO.widget.DS_JSFunction=function(A,C){if(C&&(C.constructor==Object)){for(var B in C){this[B]=C[B];}}if(!YAHOO.lang.isFunction(A)){return ;}else{this.dataFunction=A;this._init();}};YAHOO.widget.DS_JSFunction.prototype=new YAHOO.widget.DataSource();YAHOO.widget.DS_JSFunction.prototype.dataFunction=null;YAHOO.widget.DS_JSFunction.prototype.doQuery=function(C,F,D){var B=this.dataFunction;var E=[];E=B(F);if(E===null){this.dataErrorEvent.fire(this,D,F,YAHOO.widget.DataSource.ERROR_DATANULL);return ;}var A={};A.query=decodeURIComponent(F);A.results=E;this._addCacheElem(A);this.getResultsEvent.fire(this,D,F,E);C(F,E,D);return ;};YAHOO.widget.DS_JSArray=function(A,C){if(C&&(C.constructor==Object)){for(var B in C){this[B]=C[B];}}if(!YAHOO.lang.isArray(A)){return ;}else{this.data=A;this._init();}};YAHOO.widget.DS_JSArray.prototype=new YAHOO.widget.DataSource();YAHOO.widget.DS_JSArray.prototype.data=null;YAHOO.widget.DS_JSArray.prototype.doQuery=function(E,I,A){var F;var C=this.data;var J=[];var D=false;var B=this.queryMatchContains;if(I){if(!this.queryMatchCase){I=I.toLowerCase();}for(F=C.length-1;F>=0;F--){var H=[];if(YAHOO.lang.isString(C[F])){H[0]=C[F];}else{if(YAHOO.lang.isArray(C[F])){H=C[F];}}if(YAHOO.lang.isString(H[0])){var G=(this.queryMatchCase)?encodeURIComponent(H[0]).indexOf(I):encodeURIComponent(H[0]).toLowerCase().indexOf(I);if((!B&&(G===0))||(B&&(G>-1))){J.unshift(H);}}}}else{for(F=C.length-1;F>=0;F--){if(YAHOO.lang.isString(C[F])){J.unshift([C[F]]);}else{if(YAHOO.lang.isArray(C[F])){J.unshift(C[F]);}}}}this.getResultsEvent.fire(this,A,I,J);E(I,J,A);};YAHOO.register("autocomplete",YAHOO.widget.AutoComplete,{version:"2.5.2",build:"1076"});
\ No newline at end of file
diff --git a/lib/yui/autocomplete/autocomplete.js b/lib/yui/autocomplete/autocomplete.js
new file mode 100755
index 0000000000..2df22e798d
--- /dev/null
+++ b/lib/yui/autocomplete/autocomplete.js
@@ -0,0 +1,3558 @@
+/*
+Copyright (c) 2008, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 2.5.2
+*/
+ /**
+ * The AutoComplete control provides the front-end logic for text-entry suggestion and
+ * completion functionality.
+ *
+ * @module autocomplete
+ * @requires yahoo, dom, event, datasource
+ * @optional animation, connection, get
+ * @namespace YAHOO.widget
+ * @title AutoComplete Widget
+ */
+
+/****************************************************************************/
+/****************************************************************************/
+/****************************************************************************/
+
+/**
+ * The AutoComplete class provides the customizable functionality of a plug-and-play DHTML
+ * auto completion widget. Some key features:
+ *
+ *
Navigate with up/down arrow keys and/or mouse to pick a selection
+ *
The drop down container can "roll down" or "fly out" via configurable
+ * animation
+ *
UI look-and-feel customizable through CSS, including container
+ * attributes, borders, position, fonts, etc
+ *
+ *
+ * @class AutoComplete
+ * @constructor
+ * @param elInput {HTMLElement} DOM element reference of an input field.
+ * @param elInput {String} String ID of an input field.
+ * @param elContainer {HTMLElement} DOM element reference of an existing DIV.
+ * @param elContainer {String} String ID of an existing DIV.
+ * @param oDataSource {YAHOO.widget.DataSource} DataSource instance.
+ * @param oConfigs {Object} (optional) Object literal of configuration params.
+ */
+YAHOO.widget.AutoComplete = function(elInput,elContainer,oDataSource,oConfigs) {
+ if(elInput && elContainer && oDataSource) {
+ // Validate DataSource
+ if(oDataSource instanceof YAHOO.widget.DataSource) {
+ this.dataSource = oDataSource;
+ }
+ else {
+ return;
+ }
+
+ // Validate input element
+ if(YAHOO.util.Dom.inDocument(elInput)) {
+ if(YAHOO.lang.isString(elInput)) {
+ this._sName = "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput;
+ this._elTextbox = document.getElementById(elInput);
+ }
+ else {
+ this._sName = (elInput.id) ?
+ "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput.id:
+ "instance" + YAHOO.widget.AutoComplete._nIndex;
+ this._elTextbox = elInput;
+ }
+ YAHOO.util.Dom.addClass(this._elTextbox, "yui-ac-input");
+ }
+ else {
+ return;
+ }
+
+ // Validate container element
+ if(YAHOO.util.Dom.inDocument(elContainer)) {
+ if(YAHOO.lang.isString(elContainer)) {
+ this._elContainer = document.getElementById(elContainer);
+ }
+ else {
+ this._elContainer = elContainer;
+ }
+ if(this._elContainer.style.display == "none") {
+ }
+
+ // For skinning
+ var elParent = this._elContainer.parentNode;
+ var elTag = elParent.tagName.toLowerCase();
+ if(elTag == "div") {
+ YAHOO.util.Dom.addClass(elParent, "yui-ac");
+ }
+ else {
+ }
+ }
+ else {
+ return;
+ }
+
+ // Set any config params passed in to override defaults
+ if(oConfigs && (oConfigs.constructor == Object)) {
+ for(var sConfig in oConfigs) {
+ if(sConfig) {
+ this[sConfig] = oConfigs[sConfig];
+ }
+ }
+ }
+
+ // Initialization sequence
+ this._initContainer();
+ this._initProps();
+ this._initList();
+ this._initContainerHelpers();
+
+ // Set up events
+ var oSelf = this;
+ var elTextbox = this._elTextbox;
+ // Events are actually for the content module within the container
+ var elContent = this._elContent;
+
+ // Dom events
+ YAHOO.util.Event.addListener(elTextbox,"keyup",oSelf._onTextboxKeyUp,oSelf);
+ YAHOO.util.Event.addListener(elTextbox,"keydown",oSelf._onTextboxKeyDown,oSelf);
+ YAHOO.util.Event.addListener(elTextbox,"focus",oSelf._onTextboxFocus,oSelf);
+ YAHOO.util.Event.addListener(elTextbox,"blur",oSelf._onTextboxBlur,oSelf);
+ YAHOO.util.Event.addListener(elContent,"mouseover",oSelf._onContainerMouseover,oSelf);
+ YAHOO.util.Event.addListener(elContent,"mouseout",oSelf._onContainerMouseout,oSelf);
+ YAHOO.util.Event.addListener(elContent,"scroll",oSelf._onContainerScroll,oSelf);
+ YAHOO.util.Event.addListener(elContent,"resize",oSelf._onContainerResize,oSelf);
+ YAHOO.util.Event.addListener(elTextbox,"keypress",oSelf._onTextboxKeyPress,oSelf);
+ YAHOO.util.Event.addListener(window,"unload",oSelf._onWindowUnload,oSelf);
+
+ // Custom events
+ this.textboxFocusEvent = new YAHOO.util.CustomEvent("textboxFocus", this);
+ this.textboxKeyEvent = new YAHOO.util.CustomEvent("textboxKey", this);
+ this.dataRequestEvent = new YAHOO.util.CustomEvent("dataRequest", this);
+ this.dataReturnEvent = new YAHOO.util.CustomEvent("dataReturn", this);
+ this.dataErrorEvent = new YAHOO.util.CustomEvent("dataError", this);
+ this.containerExpandEvent = new YAHOO.util.CustomEvent("containerExpand", this);
+ this.typeAheadEvent = new YAHOO.util.CustomEvent("typeAhead", this);
+ this.itemMouseOverEvent = new YAHOO.util.CustomEvent("itemMouseOver", this);
+ this.itemMouseOutEvent = new YAHOO.util.CustomEvent("itemMouseOut", this);
+ this.itemArrowToEvent = new YAHOO.util.CustomEvent("itemArrowTo", this);
+ this.itemArrowFromEvent = new YAHOO.util.CustomEvent("itemArrowFrom", this);
+ this.itemSelectEvent = new YAHOO.util.CustomEvent("itemSelect", this);
+ this.unmatchedItemSelectEvent = new YAHOO.util.CustomEvent("unmatchedItemSelect", this);
+ this.selectionEnforceEvent = new YAHOO.util.CustomEvent("selectionEnforce", this);
+ this.containerCollapseEvent = new YAHOO.util.CustomEvent("containerCollapse", this);
+ this.textboxBlurEvent = new YAHOO.util.CustomEvent("textboxBlur", this);
+
+ // Finish up
+ elTextbox.setAttribute("autocomplete","off");
+ YAHOO.widget.AutoComplete._nIndex++;
+ }
+ // Required arguments were not found
+ else {
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The DataSource object that encapsulates the data used for auto completion.
+ * This object should be an inherited object from YAHOO.widget.DataSource.
+ *
+ * @property dataSource
+ * @type YAHOO.widget.DataSource
+ */
+YAHOO.widget.AutoComplete.prototype.dataSource = null;
+
+/**
+ * Number of characters that must be entered before querying for results. A negative value
+ * effectively turns off the widget. A value of 0 allows queries of null or empty string
+ * values.
+ *
+ * @property minQueryLength
+ * @type Number
+ * @default 1
+ */
+YAHOO.widget.AutoComplete.prototype.minQueryLength = 1;
+
+/**
+ * Maximum number of results to display in results container.
+ *
+ * @property maxResultsDisplayed
+ * @type Number
+ * @default 10
+ */
+YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed = 10;
+
+/**
+ * Number of seconds to delay before submitting a query request. If a query
+ * request is received before a previous one has completed its delay, the
+ * previous request is cancelled and the new request is set to the delay.
+ * Implementers should take care when setting this value very low (i.e., less
+ * than 0.2) with low latency DataSources and the typeAhead feature enabled, as
+ * fast typers may see unexpected behavior.
+ *
+ * @property queryDelay
+ * @type Number
+ * @default 0.2
+ */
+YAHOO.widget.AutoComplete.prototype.queryDelay = 0.2;
+
+/**
+ * Class name of a highlighted item within results container.
+ *
+ * @property highlightClassName
+ * @type String
+ * @default "yui-ac-highlight"
+ */
+YAHOO.widget.AutoComplete.prototype.highlightClassName = "yui-ac-highlight";
+
+/**
+ * Class name of a pre-highlighted item within results container.
+ *
+ * @property prehighlightClassName
+ * @type String
+ */
+YAHOO.widget.AutoComplete.prototype.prehighlightClassName = null;
+
+/**
+ * Query delimiter. A single character separator for multiple delimited
+ * selections. Multiple delimiter characteres may be defined as an array of
+ * strings. A null value or empty string indicates that query results cannot
+ * be delimited. This feature is not recommended if you need forceSelection to
+ * be true.
+ *
+ * @property delimChar
+ * @type String | String[]
+ */
+YAHOO.widget.AutoComplete.prototype.delimChar = null;
+
+/**
+ * Whether or not the first item in results container should be automatically highlighted
+ * on expand.
+ *
+ * @property autoHighlight
+ * @type Boolean
+ * @default true
+ */
+YAHOO.widget.AutoComplete.prototype.autoHighlight = true;
+
+/**
+ * Whether or not the input field should be automatically updated
+ * with the first query result as the user types, auto-selecting the substring
+ * that the user has not typed.
+ *
+ * @property typeAhead
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.typeAhead = false;
+
+/**
+ * Whether or not to animate the expansion/collapse of the results container in the
+ * horizontal direction.
+ *
+ * @property animHoriz
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.animHoriz = false;
+
+/**
+ * Whether or not to animate the expansion/collapse of the results container in the
+ * vertical direction.
+ *
+ * @property animVert
+ * @type Boolean
+ * @default true
+ */
+YAHOO.widget.AutoComplete.prototype.animVert = true;
+
+/**
+ * Speed of container expand/collapse animation, in seconds..
+ *
+ * @property animSpeed
+ * @type Number
+ * @default 0.3
+ */
+YAHOO.widget.AutoComplete.prototype.animSpeed = 0.3;
+
+/**
+ * Whether or not to force the user's selection to match one of the query
+ * results. Enabling this feature essentially transforms the input field into a
+ * <select> field. This feature is not recommended with delimiter character(s)
+ * defined.
+ *
+ * @property forceSelection
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.forceSelection = false;
+
+/**
+ * Whether or not to allow browsers to cache user-typed input in the input
+ * field. Disabling this feature will prevent the widget from setting the
+ * autocomplete="off" on the input field. When autocomplete="off"
+ * and users click the back button after form submission, user-typed input can
+ * be prefilled by the browser from its cache. This caching of user input may
+ * not be desired for sensitive data, such as credit card numbers, in which
+ * case, implementers should consider setting allowBrowserAutocomplete to false.
+ *
+ * @property allowBrowserAutocomplete
+ * @type Boolean
+ * @default true
+ */
+YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete = true;
+
+/**
+ * Whether or not the results container should always be displayed.
+ * Enabling this feature displays the container when the widget is instantiated
+ * and prevents the toggling of the container to a collapsed state.
+ *
+ * @property alwaysShowContainer
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.alwaysShowContainer = false;
+
+/**
+ * Whether or not to use an iFrame to layer over Windows form elements in
+ * IE. Set to true only when the results container will be on top of a
+ * <select> field in IE and thus exposed to the IE z-index bug (i.e.,
+ * 5.5 < IE < 7).
+ *
+ * @property useIFrame
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.useIFrame = false;
+
+/**
+ * Whether or not the results container should have a shadow.
+ *
+ * @property useShadow
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.AutoComplete.prototype.useShadow = false;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Public accessor to the unique name of the AutoComplete instance.
+ *
+ * @method toString
+ * @return {String} Unique name of the AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.toString = function() {
+ return "AutoComplete " + this._sName;
+};
+
+ /**
+ * Returns true if container is in an expanded state, false otherwise.
+ *
+ * @method isContainerOpen
+ * @return {Boolean} Returns true if container is in an expanded state, false otherwise.
+ */
+YAHOO.widget.AutoComplete.prototype.isContainerOpen = function() {
+ return this._bContainerOpen;
+};
+
+/**
+ * Public accessor to the internal array of DOM <li> elements that
+ * display query results within the results container.
+ *
+ * @method getListItems
+ * @return {HTMLElement[]} Array of <li> elements within the results container.
+ */
+YAHOO.widget.AutoComplete.prototype.getListItems = function() {
+ return this._aListItems;
+};
+
+/**
+ * Public accessor to the data held in an <li> element of the
+ * results container.
+ *
+ * @method getListItemData
+ * @return {Object | Object[]} Object or array of result data or null
+ */
+YAHOO.widget.AutoComplete.prototype.getListItemData = function(oListItem) {
+ if(oListItem._oResultData) {
+ return oListItem._oResultData;
+ }
+ else {
+ return false;
+ }
+};
+
+/**
+ * Sets HTML markup for the results container header. This markup will be
+ * inserted within a <div> tag with a class of "yui-ac-hd".
+ *
+ * @method setHeader
+ * @param sHeader {String} HTML markup for results container header.
+ */
+YAHOO.widget.AutoComplete.prototype.setHeader = function(sHeader) {
+ if(this._elHeader) {
+ var elHeader = this._elHeader;
+ if(sHeader) {
+ elHeader.innerHTML = sHeader;
+ elHeader.style.display = "block";
+ }
+ else {
+ elHeader.innerHTML = "";
+ elHeader.style.display = "none";
+ }
+ }
+};
+
+/**
+ * Sets HTML markup for the results container footer. This markup will be
+ * inserted within a <div> tag with a class of "yui-ac-ft".
+ *
+ * @method setFooter
+ * @param sFooter {String} HTML markup for results container footer.
+ */
+YAHOO.widget.AutoComplete.prototype.setFooter = function(sFooter) {
+ if(this._elFooter) {
+ var elFooter = this._elFooter;
+ if(sFooter) {
+ elFooter.innerHTML = sFooter;
+ elFooter.style.display = "block";
+ }
+ else {
+ elFooter.innerHTML = "";
+ elFooter.style.display = "none";
+ }
+ }
+};
+
+/**
+ * Sets HTML markup for the results container body. This markup will be
+ * inserted within a <div> tag with a class of "yui-ac-bd".
+ *
+ * @method setBody
+ * @param sBody {String} HTML markup for results container body.
+ */
+YAHOO.widget.AutoComplete.prototype.setBody = function(sBody) {
+ if(this._elBody) {
+ var elBody = this._elBody;
+ if(sBody) {
+ elBody.innerHTML = sBody;
+ elBody.style.display = "block";
+ elBody.style.display = "block";
+ }
+ else {
+ elBody.innerHTML = "";
+ elBody.style.display = "none";
+ }
+ this._maxResultsDisplayed = 0;
+ }
+};
+
+/**
+ * Overridable method that converts a result item object into HTML markup
+ * for display. Return data values are accessible via the oResultItem object,
+ * and the key return value will always be oResultItem[0]. Markup will be
+ * displayed within <li> element tags in the container.
+ *
+ * @method formatResult
+ * @param oResultItem {Object} Result item representing one query result. Data is held in an array.
+ * @param sQuery {String} The current query string.
+ * @return {String} HTML markup of formatted result data.
+ */
+YAHOO.widget.AutoComplete.prototype.formatResult = function(oResultItem, sQuery) {
+ var sResult = oResultItem[0];
+ if(sResult) {
+ return sResult;
+ }
+ else {
+ return "";
+ }
+};
+
+/**
+ * Overridable method called before container expands allows implementers to access data
+ * and DOM elements.
+ *
+ * @method doBeforeExpandContainer
+ * @param elTextbox {HTMLElement} The text input box.
+ * @param elContainer {HTMLElement} The container element.
+ * @param sQuery {String} The query string.
+ * @param aResults {Object[]} An array of query results.
+ * @return {Boolean} Return true to continue expanding container, false to cancel the expand.
+ */
+YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer = function(elTextbox, elContainer, sQuery, aResults) {
+ return true;
+};
+
+/**
+ * Makes query request to the DataSource.
+ *
+ * @method sendQuery
+ * @param sQuery {String} Query string.
+ */
+YAHOO.widget.AutoComplete.prototype.sendQuery = function(sQuery) {
+ this._sendQuery(sQuery);
+};
+
+/**
+ * Overridable method gives implementers access to the query before it gets sent.
+ *
+ * @method doBeforeSendQuery
+ * @param sQuery {String} Query string.
+ * @return {String} Query string.
+ */
+YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery = function(sQuery) {
+ return sQuery;
+};
+
+/**
+ * Nulls out the entire AutoComplete instance and related objects, removes attached
+ * event listeners, and clears out DOM elements inside the container. After
+ * calling this method, the instance reference should be expliclitly nulled by
+ * implementer, as in myDataTable = null. Use with caution!
+ *
+ * @method destroy
+ */
+YAHOO.widget.AutoComplete.prototype.destroy = function() {
+ var instanceName = this.toString();
+ var elInput = this._elTextbox;
+ var elContainer = this._elContainer;
+
+ // Unhook custom events
+ this.textboxFocusEvent.unsubscribeAll();
+ this.textboxKeyEvent.unsubscribeAll();
+ this.dataRequestEvent.unsubscribeAll();
+ this.dataReturnEvent.unsubscribeAll();
+ this.dataErrorEvent.unsubscribeAll();
+ this.containerExpandEvent.unsubscribeAll();
+ this.typeAheadEvent.unsubscribeAll();
+ this.itemMouseOverEvent.unsubscribeAll();
+ this.itemMouseOutEvent.unsubscribeAll();
+ this.itemArrowToEvent.unsubscribeAll();
+ this.itemArrowFromEvent.unsubscribeAll();
+ this.itemSelectEvent.unsubscribeAll();
+ this.unmatchedItemSelectEvent.unsubscribeAll();
+ this.selectionEnforceEvent.unsubscribeAll();
+ this.containerCollapseEvent.unsubscribeAll();
+ this.textboxBlurEvent.unsubscribeAll();
+
+ // Unhook DOM events
+ YAHOO.util.Event.purgeElement(elInput, true);
+ YAHOO.util.Event.purgeElement(elContainer, true);
+
+ // Remove DOM elements
+ elContainer.innerHTML = "";
+
+ // Null out objects
+ for(var key in this) {
+ if(YAHOO.lang.hasOwnProperty(this, key)) {
+ this[key] = null;
+ }
+ }
+
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public events
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Fired when the input field receives focus.
+ *
+ * @event textboxFocusEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.textboxFocusEvent = null;
+
+/**
+ * Fired when the input field receives key input.
+ *
+ * @event textboxKeyEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param nKeycode {Number} The keycode number.
+ */
+YAHOO.widget.AutoComplete.prototype.textboxKeyEvent = null;
+
+/**
+ * Fired when the AutoComplete instance makes a query to the DataSource.
+ *
+ * @event dataRequestEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param sQuery {String} The query string.
+ */
+YAHOO.widget.AutoComplete.prototype.dataRequestEvent = null;
+
+/**
+ * Fired when the AutoComplete instance receives query results from the data
+ * source.
+ *
+ * @event dataReturnEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param sQuery {String} The query string.
+ * @param aResults {Object[]} Results array.
+ */
+YAHOO.widget.AutoComplete.prototype.dataReturnEvent = null;
+
+/**
+ * Fired when the AutoComplete instance does not receive query results from the
+ * DataSource due to an error.
+ *
+ * @event dataErrorEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param sQuery {String} The query string.
+ */
+YAHOO.widget.AutoComplete.prototype.dataErrorEvent = null;
+
+/**
+ * Fired when the results container is expanded.
+ *
+ * @event containerExpandEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.containerExpandEvent = null;
+
+/**
+ * Fired when the input field has been prefilled by the type-ahead
+ * feature.
+ *
+ * @event typeAheadEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param sQuery {String} The query string.
+ * @param sPrefill {String} The prefill string.
+ */
+YAHOO.widget.AutoComplete.prototype.typeAheadEvent = null;
+
+/**
+ * Fired when result item has been moused over.
+ *
+ * @event itemMouseOverEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param elItem {HTMLElement} The <li> element item moused to.
+ */
+YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent = null;
+
+/**
+ * Fired when result item has been moused out.
+ *
+ * @event itemMouseOutEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param elItem {HTMLElement} The <li> element item moused from.
+ */
+YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent = null;
+
+/**
+ * Fired when result item has been arrowed to.
+ *
+ * @event itemArrowToEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param elItem {HTMLElement} The <li> element item arrowed to.
+ */
+YAHOO.widget.AutoComplete.prototype.itemArrowToEvent = null;
+
+/**
+ * Fired when result item has been arrowed away from.
+ *
+ * @event itemArrowFromEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param elItem {HTMLElement} The <li> element item arrowed from.
+ */
+YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent = null;
+
+/**
+ * Fired when an item is selected via mouse click, ENTER key, or TAB key.
+ *
+ * @event itemSelectEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @param elItem {HTMLElement} The selected <li> element item.
+ * @param oData {Object} The data returned for the item, either as an object,
+ * or mapped from the schema into an array.
+ */
+YAHOO.widget.AutoComplete.prototype.itemSelectEvent = null;
+
+/**
+ * Fired when a user selection does not match any of the displayed result items.
+ *
+ * @event unmatchedItemSelectEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent = null;
+
+/**
+ * Fired if forceSelection is enabled and the user's input has been cleared
+ * because it did not match one of the returned query results.
+ *
+ * @event selectionEnforceEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent = null;
+
+/**
+ * Fired when the results container is collapsed.
+ *
+ * @event containerCollapseEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.containerCollapseEvent = null;
+
+/**
+ * Fired when the input field loses focus.
+ *
+ * @event textboxBlurEvent
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ */
+YAHOO.widget.AutoComplete.prototype.textboxBlurEvent = null;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Internal class variable to index multiple AutoComplete instances.
+ *
+ * @property _nIndex
+ * @type Number
+ * @default 0
+ * @private
+ */
+YAHOO.widget.AutoComplete._nIndex = 0;
+
+/**
+ * Name of AutoComplete instance.
+ *
+ * @property _sName
+ * @type String
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._sName = null;
+
+/**
+ * Text input field DOM element.
+ *
+ * @property _elTextbox
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elTextbox = null;
+
+/**
+ * Container DOM element.
+ *
+ * @property _elContainer
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elContainer = null;
+
+/**
+ * Reference to content element within container element.
+ *
+ * @property _elContent
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elContent = null;
+
+/**
+ * Reference to header element within content element.
+ *
+ * @property _elHeader
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elHeader = null;
+
+/**
+ * Reference to body element within content element.
+ *
+ * @property _elBody
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elBody = null;
+
+/**
+ * Reference to footer element within content element.
+ *
+ * @property _elFooter
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elFooter = null;
+
+/**
+ * Reference to shadow element within container element.
+ *
+ * @property _elShadow
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elShadow = null;
+
+/**
+ * Reference to iframe element within container element.
+ *
+ * @property _elIFrame
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._elIFrame = null;
+
+/**
+ * Whether or not the input field is currently in focus. If query results come back
+ * but the user has already moved on, do not proceed with auto complete behavior.
+ *
+ * @property _bFocused
+ * @type Boolean
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._bFocused = true;
+
+/**
+ * Animation instance for container expand/collapse.
+ *
+ * @property _oAnim
+ * @type Boolean
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._oAnim = null;
+
+/**
+ * Whether or not the results container is currently open.
+ *
+ * @property _bContainerOpen
+ * @type Boolean
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._bContainerOpen = false;
+
+/**
+ * Whether or not the mouse is currently over the results
+ * container. This is necessary in order to prevent clicks on container items
+ * from being text input field blur events.
+ *
+ * @property _bOverContainer
+ * @type Boolean
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._bOverContainer = false;
+
+/**
+ * Array of <li> elements references that contain query results within the
+ * results container.
+ *
+ * @property _aListItems
+ * @type HTMLElement[]
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._aListItems = null;
+
+/**
+ * Number of <li> elements currently displayed in results container.
+ *
+ * @property _nDisplayedItems
+ * @type Number
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._nDisplayedItems = 0;
+
+/**
+ * Internal count of <li> elements displayed and hidden in results container.
+ *
+ * @property _maxResultsDisplayed
+ * @type Number
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._maxResultsDisplayed = 0;
+
+/**
+ * Current query string
+ *
+ * @property _sCurQuery
+ * @type String
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._sCurQuery = null;
+
+/**
+ * Past queries this session (for saving delimited queries).
+ *
+ * @property _sSavedQuery
+ * @type String
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._sSavedQuery = null;
+
+/**
+ * Pointer to the currently highlighted <li> element in the container.
+ *
+ * @property _oCurItem
+ * @type HTMLElement
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._oCurItem = null;
+
+/**
+ * Whether or not an item has been selected since the container was populated
+ * with results. Reset to false by _populateList, and set to true when item is
+ * selected.
+ *
+ * @property _bItemSelected
+ * @type Boolean
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._bItemSelected = false;
+
+/**
+ * Key code of the last key pressed in textbox.
+ *
+ * @property _nKeyCode
+ * @type Number
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._nKeyCode = null;
+
+/**
+ * Delay timeout ID.
+ *
+ * @property _nDelayID
+ * @type Number
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._nDelayID = -1;
+
+/**
+ * Src to iFrame used when useIFrame = true. Supports implementations over SSL
+ * as well.
+ *
+ * @property _iFrameSrc
+ * @type String
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._iFrameSrc = "javascript:false;";
+
+/**
+ * For users typing via certain IMEs, queries must be triggered by intervals,
+ * since key events yet supported across all browsers for all IMEs.
+ *
+ * @property _queryInterval
+ * @type Object
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._queryInterval = null;
+
+/**
+ * Internal tracker to last known textbox value, used to determine whether or not
+ * to trigger a query via interval for certain IME users.
+ *
+ * @event _sLastTextboxValue
+ * @type String
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._sLastTextboxValue = null;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Updates and validates latest public config properties.
+ *
+ * @method __initProps
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._initProps = function() {
+ // Correct any invalid values
+ var minQueryLength = this.minQueryLength;
+ if(!YAHOO.lang.isNumber(minQueryLength)) {
+ this.minQueryLength = 1;
+ }
+ var maxResultsDisplayed = this.maxResultsDisplayed;
+ if(!YAHOO.lang.isNumber(maxResultsDisplayed) || (maxResultsDisplayed < 1)) {
+ this.maxResultsDisplayed = 10;
+ }
+ var queryDelay = this.queryDelay;
+ if(!YAHOO.lang.isNumber(queryDelay) || (queryDelay < 0)) {
+ this.queryDelay = 0.2;
+ }
+ var delimChar = this.delimChar;
+ if(YAHOO.lang.isString(delimChar) && (delimChar.length > 0)) {
+ this.delimChar = [delimChar];
+ }
+ else if(!YAHOO.lang.isArray(delimChar)) {
+ this.delimChar = null;
+ }
+ var animSpeed = this.animSpeed;
+ if((this.animHoriz || this.animVert) && YAHOO.util.Anim) {
+ if(!YAHOO.lang.isNumber(animSpeed) || (animSpeed < 0)) {
+ this.animSpeed = 0.3;
+ }
+ if(!this._oAnim ) {
+ this._oAnim = new YAHOO.util.Anim(this._elContent, {}, this.animSpeed);
+ }
+ else {
+ this._oAnim.duration = this.animSpeed;
+ }
+ }
+ if(this.forceSelection && delimChar) {
+ }
+};
+
+/**
+ * Initializes the results container helpers if they are enabled and do
+ * not exist
+ *
+ * @method _initContainerHelpers
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._initContainerHelpers = function() {
+ if(this.useShadow && !this._elShadow) {
+ var elShadow = document.createElement("div");
+ elShadow.className = "yui-ac-shadow";
+ this._elShadow = this._elContainer.appendChild(elShadow);
+ }
+ if(this.useIFrame && !this._elIFrame) {
+ var elIFrame = document.createElement("iframe");
+ elIFrame.src = this._iFrameSrc;
+ elIFrame.frameBorder = 0;
+ elIFrame.scrolling = "no";
+ elIFrame.style.position = "absolute";
+ elIFrame.style.width = "100%";
+ elIFrame.style.height = "100%";
+ elIFrame.tabIndex = -1;
+ this._elIFrame = this._elContainer.appendChild(elIFrame);
+ }
+};
+
+/**
+ * Initializes the results container once at object creation
+ *
+ * @method _initContainer
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._initContainer = function() {
+ YAHOO.util.Dom.addClass(this._elContainer, "yui-ac-container");
+
+ if(!this._elContent) {
+ // The elContent div helps size the iframe and shadow properly
+ var elContent = document.createElement("div");
+ elContent.className = "yui-ac-content";
+ elContent.style.display = "none";
+ this._elContent = this._elContainer.appendChild(elContent);
+
+ var elHeader = document.createElement("div");
+ elHeader.className = "yui-ac-hd";
+ elHeader.style.display = "none";
+ this._elHeader = this._elContent.appendChild(elHeader);
+
+ var elBody = document.createElement("div");
+ elBody.className = "yui-ac-bd";
+ this._elBody = this._elContent.appendChild(elBody);
+
+ var elFooter = document.createElement("div");
+ elFooter.className = "yui-ac-ft";
+ elFooter.style.display = "none";
+ this._elFooter = this._elContent.appendChild(elFooter);
+ }
+ else {
+ }
+};
+
+/**
+ * Clears out contents of container body and creates up to
+ * YAHOO.widget.AutoComplete#maxResultsDisplayed <li> elements in an
+ * <ul> element.
+ *
+ * @method _initList
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._initList = function() {
+ this._aListItems = [];
+ while(this._elBody.hasChildNodes()) {
+ var oldListItems = this.getListItems();
+ if(oldListItems) {
+ for(var oldi = oldListItems.length-1; oldi >= 0; oldi--) {
+ oldListItems[oldi] = null;
+ }
+ }
+ this._elBody.innerHTML = "";
+ }
+
+ var oList = document.createElement("ul");
+ oList = this._elBody.appendChild(oList);
+ for(var i=0; i= 18 && nKeyCode <= 20) || // alt,pause/break,caps lock
+ (nKeyCode == 27) || // esc
+ (nKeyCode >= 33 && nKeyCode <= 35) || // page up,page down,end
+ /*(nKeyCode >= 36 && nKeyCode <= 38) || // home,left,up
+ (nKeyCode == 40) || // down*/
+ (nKeyCode >= 36 && nKeyCode <= 40) || // home,left,up, right, down
+ (nKeyCode >= 44 && nKeyCode <= 45)) { // print screen,insert
+ return true;
+ }
+ return false;
+};
+
+/**
+ * Makes query request to the DataSource.
+ *
+ * @method _sendQuery
+ * @param sQuery {String} Query string.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._sendQuery = function(sQuery) {
+ // Widget has been effectively turned off
+ if(this.minQueryLength == -1) {
+ this._toggleContainer(false);
+ return;
+ }
+ // Delimiter has been enabled
+ var aDelimChar = (this.delimChar) ? this.delimChar : null;
+ if(aDelimChar) {
+ // Loop through all possible delimiters and find the latest one
+ // A " " may be a false positive if they are defined as delimiters AND
+ // are used to separate delimited queries
+ var nDelimIndex = -1;
+ for(var i = aDelimChar.length-1; i >= 0; i--) {
+ var nNewIndex = sQuery.lastIndexOf(aDelimChar[i]);
+ if(nNewIndex > nDelimIndex) {
+ nDelimIndex = nNewIndex;
+ }
+ }
+ // If we think the last delimiter is a space (" "), make sure it is NOT
+ // a false positive by also checking the char directly before it
+ if(aDelimChar[i] == " ") {
+ for (var j = aDelimChar.length-1; j >= 0; j--) {
+ if(sQuery[nDelimIndex - 1] == aDelimChar[j]) {
+ nDelimIndex--;
+ break;
+ }
+ }
+ }
+ // A delimiter has been found so extract the latest query
+ if(nDelimIndex > -1) {
+ var nQueryStart = nDelimIndex + 1;
+ // Trim any white space from the beginning...
+ while(sQuery.charAt(nQueryStart) == " ") {
+ nQueryStart += 1;
+ }
+ // ...and save the rest of the string for later
+ this._sSavedQuery = sQuery.substring(0,nQueryStart);
+ // Here is the query itself
+ sQuery = sQuery.substr(nQueryStart);
+ }
+ else if(sQuery.indexOf(this._sSavedQuery) < 0){
+ this._sSavedQuery = null;
+ }
+ }
+
+ // Don't search queries that are too short
+ if((sQuery && (sQuery.length < this.minQueryLength)) || (!sQuery && this.minQueryLength > 0)) {
+ if(this._nDelayID != -1) {
+ clearTimeout(this._nDelayID);
+ }
+ this._toggleContainer(false);
+ return;
+ }
+
+ sQuery = encodeURIComponent(sQuery);
+ this._nDelayID = -1; // Reset timeout ID because request has been made
+ sQuery = this.doBeforeSendQuery(sQuery);
+ this.dataRequestEvent.fire(this, sQuery);
+ this.dataSource.getResults(this._populateList, sQuery, this);
+};
+
+/**
+ * Populates the array of <li> elements in the container with query
+ * results. This method is passed to YAHOO.widget.DataSource#getResults as a
+ * callback function so results from the DataSource instance are returned to the
+ * AutoComplete instance.
+ *
+ * @method _populateList
+ * @param sQuery {String} The query string.
+ * @param aResults {Object[]} An array of query result objects from the DataSource.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._populateList = function(sQuery, aResults, oSelf) {
+ if(aResults === null) {
+ oSelf.dataErrorEvent.fire(oSelf, sQuery);
+ }
+ if(!oSelf._bFocused || !aResults) {
+ return;
+ }
+
+ var isOpera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);
+ var contentStyle = oSelf._elContent.style;
+ contentStyle.width = (!isOpera) ? null : "";
+ contentStyle.height = (!isOpera) ? null : "";
+
+ var sCurQuery = decodeURIComponent(sQuery);
+ oSelf._sCurQuery = sCurQuery;
+ oSelf._bItemSelected = false;
+
+ if(oSelf._maxResultsDisplayed != oSelf.maxResultsDisplayed) {
+ oSelf._initList();
+ }
+
+ var nItems = Math.min(aResults.length,oSelf.maxResultsDisplayed);
+ oSelf._nDisplayedItems = nItems;
+ if(nItems > 0) {
+ oSelf._initContainerHelpers();
+ var aItems = oSelf._aListItems;
+
+ // Fill items with data
+ for(var i = nItems-1; i >= 0; i--) {
+ var oItemi = aItems[i];
+ var oResultItemi = aResults[i];
+ oItemi.innerHTML = oSelf.formatResult(oResultItemi, sCurQuery);
+ oItemi.style.display = "list-item";
+ oItemi._sResultKey = oResultItemi[0];
+ oItemi._oResultData = oResultItemi;
+
+ }
+
+ // Empty out remaining items if any
+ for(var j = aItems.length-1; j >= nItems ; j--) {
+ var oItemj = aItems[j];
+ oItemj.innerHTML = null;
+ oItemj.style.display = "none";
+ oItemj._sResultKey = null;
+ oItemj._oResultData = null;
+ }
+
+ // Expand the container
+ var ok = oSelf.doBeforeExpandContainer(oSelf._elTextbox, oSelf._elContainer, sQuery, aResults);
+ oSelf._toggleContainer(ok);
+
+ if(oSelf.autoHighlight) {
+ // Go to the first item
+ var oFirstItem = aItems[0];
+ oSelf._toggleHighlight(oFirstItem,"to");
+ oSelf.itemArrowToEvent.fire(oSelf, oFirstItem);
+ oSelf._typeAhead(oFirstItem,sQuery);
+ }
+ else {
+ oSelf._oCurItem = null;
+ }
+ }
+ else {
+ oSelf._toggleContainer(false);
+ }
+ oSelf.dataReturnEvent.fire(oSelf, sQuery, aResults);
+
+};
+
+/**
+ * When forceSelection is true and the user attempts
+ * leave the text input box without selecting an item from the query results,
+ * the user selection is cleared.
+ *
+ * @method _clearSelection
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._clearSelection = function() {
+ var sValue = this._elTextbox.value;
+ var sChar = (this.delimChar) ? this.delimChar[0] : null;
+ var nIndex = (sChar) ? sValue.lastIndexOf(sChar, sValue.length-2) : -1;
+ if(nIndex > -1) {
+ this._elTextbox.value = sValue.substring(0,nIndex);
+ }
+ else {
+ this._elTextbox.value = "";
+ }
+ this._sSavedQuery = this._elTextbox.value;
+
+ // Fire custom event
+ this.selectionEnforceEvent.fire(this);
+};
+
+/**
+ * Whether or not user-typed value in the text input box matches any of the
+ * query results.
+ *
+ * @method _textMatchesOption
+ * @return {HTMLElement} Matching list item element if user-input text matches
+ * a result, null otherwise.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._textMatchesOption = function() {
+ var foundMatch = null;
+
+ for(var i = this._nDisplayedItems-1; i >= 0 ; i--) {
+ var oItem = this._aListItems[i];
+ var sMatch = oItem._sResultKey.toLowerCase();
+ if(sMatch == this._sCurQuery.toLowerCase()) {
+ foundMatch = oItem;
+ break;
+ }
+ }
+ return(foundMatch);
+};
+
+/**
+ * Updates in the text input box with the first query result as the user types,
+ * selecting the substring that the user has not typed.
+ *
+ * @method _typeAhead
+ * @param oItem {HTMLElement} The <li> element item whose data populates the input field.
+ * @param sQuery {String} Query string.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._typeAhead = function(oItem, sQuery) {
+ // Don't update if turned off
+ if(!this.typeAhead || (this._nKeyCode == 8)) {
+ return;
+ }
+
+ var elTextbox = this._elTextbox;
+ var sValue = this._elTextbox.value; // any saved queries plus what user has typed
+
+ // Don't update with type-ahead if text selection is not supported
+ if(!elTextbox.setSelectionRange && !elTextbox.createTextRange) {
+ return;
+ }
+
+ // Select the portion of text that the user has not typed
+ var nStart = sValue.length;
+ this._updateValue(oItem);
+ var nEnd = elTextbox.value.length;
+ this._selectText(elTextbox,nStart,nEnd);
+ var sPrefill = elTextbox.value.substr(nStart,nEnd);
+ this.typeAheadEvent.fire(this,sQuery,sPrefill);
+};
+
+/**
+ * Selects text in the input field.
+ *
+ * @method _selectText
+ * @param elTextbox {HTMLElement} Text input box element in which to select text.
+ * @param nStart {Number} Starting index of text string to select.
+ * @param nEnd {Number} Ending index of text selection.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._selectText = function(elTextbox, nStart, nEnd) {
+ if(elTextbox.setSelectionRange) { // For Mozilla
+ elTextbox.setSelectionRange(nStart,nEnd);
+ }
+ else if(elTextbox.createTextRange) { // For IE
+ var oTextRange = elTextbox.createTextRange();
+ oTextRange.moveStart("character", nStart);
+ oTextRange.moveEnd("character", nEnd-elTextbox.value.length);
+ oTextRange.select();
+ }
+ else {
+ elTextbox.select();
+ }
+};
+
+/**
+ * Syncs results container with its helpers.
+ *
+ * @method _toggleContainerHelpers
+ * @param bShow {Boolean} True if container is expanded, false if collapsed
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers = function(bShow) {
+ var bFireEvent = false;
+ var width = this._elContent.offsetWidth + "px";
+ var height = this._elContent.offsetHeight + "px";
+
+ if(this.useIFrame && this._elIFrame) {
+ bFireEvent = true;
+ if(bShow) {
+ this._elIFrame.style.width = width;
+ this._elIFrame.style.height = height;
+ }
+ else {
+ this._elIFrame.style.width = 0;
+ this._elIFrame.style.height = 0;
+ }
+ }
+ if(this.useShadow && this._elShadow) {
+ bFireEvent = true;
+ if(bShow) {
+ this._elShadow.style.width = width;
+ this._elShadow.style.height = height;
+ }
+ else {
+ this._elShadow.style.width = 0;
+ this._elShadow.style.height = 0;
+ }
+ }
+};
+
+/**
+ * Animates expansion or collapse of the container.
+ *
+ * @method _toggleContainer
+ * @param bShow {Boolean} True if container should be expanded, false if container should be collapsed
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._toggleContainer = function(bShow) {
+ var elContainer = this._elContainer;
+
+ // Implementer has container always open so don't mess with it
+ if(this.alwaysShowContainer && this._bContainerOpen) {
+ return;
+ }
+
+ // Clear contents of container
+ if(!bShow) {
+ this._elContent.scrollTop = 0;
+ var aItems = this._aListItems;
+
+ if(aItems && (aItems.length > 0)) {
+ for(var i = aItems.length-1; i >= 0 ; i--) {
+ aItems[i].style.display = "none";
+ }
+ }
+
+ if(this._oCurItem) {
+ this._toggleHighlight(this._oCurItem,"from");
+ }
+
+ this._oCurItem = null;
+ this._nDisplayedItems = 0;
+ this._sCurQuery = null;
+ }
+
+ // Container is already closed
+ if(!bShow && !this._bContainerOpen) {
+ this._elContent.style.display = "none";
+ return;
+ }
+
+ // If animation is enabled...
+ var oAnim = this._oAnim;
+ if(oAnim && oAnim.getEl() && (this.animHoriz || this.animVert)) {
+ // If helpers need to be collapsed, do it right away...
+ // but if helpers need to be expanded, wait until after the container expands
+ if(!bShow) {
+ this._toggleContainerHelpers(bShow);
+ }
+
+ if(oAnim.isAnimated()) {
+ oAnim.stop();
+ }
+
+ // Clone container to grab current size offscreen
+ var oClone = this._elContent.cloneNode(true);
+ elContainer.appendChild(oClone);
+ oClone.style.top = "-9000px";
+ oClone.style.display = "block";
+
+ // Current size of the container is the EXPANDED size
+ var wExp = oClone.offsetWidth;
+ var hExp = oClone.offsetHeight;
+
+ // Calculate COLLAPSED sizes based on horiz and vert anim
+ var wColl = (this.animHoriz) ? 0 : wExp;
+ var hColl = (this.animVert) ? 0 : hExp;
+
+ // Set animation sizes
+ oAnim.attributes = (bShow) ?
+ {width: { to: wExp }, height: { to: hExp }} :
+ {width: { to: wColl}, height: { to: hColl }};
+
+ // If opening anew, set to a collapsed size...
+ if(bShow && !this._bContainerOpen) {
+ this._elContent.style.width = wColl+"px";
+ this._elContent.style.height = hColl+"px";
+ }
+ // Else, set it to its last known size.
+ else {
+ this._elContent.style.width = wExp+"px";
+ this._elContent.style.height = hExp+"px";
+ }
+
+ elContainer.removeChild(oClone);
+ oClone = null;
+
+ var oSelf = this;
+ var onAnimComplete = function() {
+ // Finish the collapse
+ oAnim.onComplete.unsubscribeAll();
+
+ if(bShow) {
+ oSelf.containerExpandEvent.fire(oSelf);
+ }
+ else {
+ oSelf._elContent.style.display = "none";
+ oSelf.containerCollapseEvent.fire(oSelf);
+ }
+ oSelf._toggleContainerHelpers(bShow);
+ };
+
+ // Display container and animate it
+ this._elContent.style.display = "block";
+ oAnim.onComplete.subscribe(onAnimComplete);
+ oAnim.animate();
+ this._bContainerOpen = bShow;
+ }
+ // Else don't animate, just show or hide
+ else {
+ if(bShow) {
+ this._elContent.style.display = "block";
+ this.containerExpandEvent.fire(this);
+ }
+ else {
+ this._elContent.style.display = "none";
+ this.containerCollapseEvent.fire(this);
+ }
+ this._toggleContainerHelpers(bShow);
+ this._bContainerOpen = bShow;
+ }
+
+};
+
+/**
+ * Toggles the highlight on or off for an item in the container, and also cleans
+ * up highlighting of any previous item.
+ *
+ * @method _toggleHighlight
+ * @param oNewItem {HTMLElement} The <li> element item to receive highlight behavior.
+ * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._toggleHighlight = function(oNewItem, sType) {
+ var sHighlight = this.highlightClassName;
+ if(this._oCurItem) {
+ // Remove highlight from old item
+ YAHOO.util.Dom.removeClass(this._oCurItem, sHighlight);
+ }
+
+ if((sType == "to") && sHighlight) {
+ // Apply highlight to new item
+ YAHOO.util.Dom.addClass(oNewItem, sHighlight);
+ this._oCurItem = oNewItem;
+ }
+};
+
+/**
+ * Toggles the pre-highlight on or off for an item in the container.
+ *
+ * @method _togglePrehighlight
+ * @param oNewItem {HTMLElement} The <li> element item to receive highlight behavior.
+ * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._togglePrehighlight = function(oNewItem, sType) {
+ if(oNewItem == this._oCurItem) {
+ return;
+ }
+
+ var sPrehighlight = this.prehighlightClassName;
+ if((sType == "mouseover") && sPrehighlight) {
+ // Apply prehighlight to new item
+ YAHOO.util.Dom.addClass(oNewItem, sPrehighlight);
+ }
+ else {
+ // Remove prehighlight from old item
+ YAHOO.util.Dom.removeClass(oNewItem, sPrehighlight);
+ }
+};
+
+/**
+ * Updates the text input box value with selected query result. If a delimiter
+ * has been defined, then the value gets appended with the delimiter.
+ *
+ * @method _updateValue
+ * @param oItem {HTMLElement} The <li> element item with which to update the value.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._updateValue = function(oItem) {
+ var elTextbox = this._elTextbox;
+ var sDelimChar = (this.delimChar) ? (this.delimChar[0] || this.delimChar) : null;
+ var sSavedQuery = this._sSavedQuery;
+ var sResultKey = oItem._sResultKey;
+ elTextbox.focus();
+
+ // First clear text field
+ elTextbox.value = "";
+ // Grab data to put into text field
+ if(sDelimChar) {
+ if(sSavedQuery) {
+ elTextbox.value = sSavedQuery;
+ }
+ elTextbox.value += sResultKey + sDelimChar;
+ if(sDelimChar != " ") {
+ elTextbox.value += " ";
+ }
+ }
+ else { elTextbox.value = sResultKey; }
+
+ // scroll to bottom of textarea if necessary
+ if(elTextbox.type == "textarea") {
+ elTextbox.scrollTop = elTextbox.scrollHeight;
+ }
+
+ // move cursor to end
+ var end = elTextbox.value.length;
+ this._selectText(elTextbox,end,end);
+
+ this._oCurItem = oItem;
+};
+
+/**
+ * Selects a result item from the container
+ *
+ * @method _selectItem
+ * @param oItem {HTMLElement} The selected <li> element item.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._selectItem = function(oItem) {
+ this._bItemSelected = true;
+ this._updateValue(oItem);
+ this._cancelIntervalDetection(this);
+ this.itemSelectEvent.fire(this, oItem, oItem._oResultData);
+ this._toggleContainer(false);
+};
+
+/**
+ * If an item is highlighted in the container, the right arrow key jumps to the
+ * end of the textbox and selects the highlighted item, otherwise the container
+ * is closed.
+ *
+ * @method _jumpSelection
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._jumpSelection = function() {
+ if(this._oCurItem) {
+ this._selectItem(this._oCurItem);
+ }
+ else {
+ this._toggleContainer(false);
+ }
+};
+
+/**
+ * Triggered by up and down arrow keys, changes the current highlighted
+ * <li> element item. Scrolls container if necessary.
+ *
+ * @method _moveSelection
+ * @param nKeyCode {Number} Code of key pressed.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._moveSelection = function(nKeyCode) {
+ if(this._bContainerOpen) {
+ // Determine current item's id number
+ var oCurItem = this._oCurItem;
+ var nCurItemIndex = -1;
+
+ if(oCurItem) {
+ nCurItemIndex = oCurItem._nItemIndex;
+ }
+
+ var nNewItemIndex = (nKeyCode == 40) ?
+ (nCurItemIndex + 1) : (nCurItemIndex - 1);
+
+ // Out of bounds
+ if(nNewItemIndex < -2 || nNewItemIndex >= this._nDisplayedItems) {
+ return;
+ }
+
+ if(oCurItem) {
+ // Unhighlight current item
+ this._toggleHighlight(oCurItem, "from");
+ this.itemArrowFromEvent.fire(this, oCurItem);
+ }
+ if(nNewItemIndex == -1) {
+ // Go back to query (remove type-ahead string)
+ if(this.delimChar && this._sSavedQuery) {
+ if(!this._textMatchesOption()) {
+ this._elTextbox.value = this._sSavedQuery;
+ }
+ else {
+ this._elTextbox.value = this._sSavedQuery + this._sCurQuery;
+ }
+ }
+ else {
+ this._elTextbox.value = this._sCurQuery;
+ }
+ this._oCurItem = null;
+ return;
+ }
+ if(nNewItemIndex == -2) {
+ // Close container
+ this._toggleContainer(false);
+ return;
+ }
+
+ var oNewItem = this._aListItems[nNewItemIndex];
+
+ // Scroll the container if necessary
+ var elContent = this._elContent;
+ var scrollOn = ((YAHOO.util.Dom.getStyle(elContent,"overflow") == "auto") ||
+ (YAHOO.util.Dom.getStyle(elContent,"overflowY") == "auto"));
+ if(scrollOn && (nNewItemIndex > -1) &&
+ (nNewItemIndex < this._nDisplayedItems)) {
+ // User is keying down
+ if(nKeyCode == 40) {
+ // Bottom of selected item is below scroll area...
+ if((oNewItem.offsetTop+oNewItem.offsetHeight) > (elContent.scrollTop + elContent.offsetHeight)) {
+ // Set bottom of scroll area to bottom of selected item
+ elContent.scrollTop = (oNewItem.offsetTop+oNewItem.offsetHeight) - elContent.offsetHeight;
+ }
+ // Bottom of selected item is above scroll area...
+ else if((oNewItem.offsetTop+oNewItem.offsetHeight) < elContent.scrollTop) {
+ // Set top of selected item to top of scroll area
+ elContent.scrollTop = oNewItem.offsetTop;
+
+ }
+ }
+ // User is keying up
+ else {
+ // Top of selected item is above scroll area
+ if(oNewItem.offsetTop < elContent.scrollTop) {
+ // Set top of scroll area to top of selected item
+ this._elContent.scrollTop = oNewItem.offsetTop;
+ }
+ // Top of selected item is below scroll area
+ else if(oNewItem.offsetTop > (elContent.scrollTop + elContent.offsetHeight)) {
+ // Set bottom of selected item to bottom of scroll area
+ this._elContent.scrollTop = (oNewItem.offsetTop+oNewItem.offsetHeight) - elContent.offsetHeight;
+ }
+ }
+ }
+
+ this._toggleHighlight(oNewItem, "to");
+ this.itemArrowToEvent.fire(this, oNewItem);
+ if(this.typeAhead) {
+ this._updateValue(oNewItem);
+ }
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private event handlers
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Handles <li> element mouseover events in the container.
+ *
+ * @method _onItemMouseover
+ * @param v {HTMLEvent} The mouseover event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onItemMouseover = function(v,oSelf) {
+ if(oSelf.prehighlightClassName) {
+ oSelf._togglePrehighlight(this,"mouseover");
+ }
+ else {
+ oSelf._toggleHighlight(this,"to");
+ }
+
+ oSelf.itemMouseOverEvent.fire(oSelf, this);
+};
+
+/**
+ * Handles <li> element mouseout events in the container.
+ *
+ * @method _onItemMouseout
+ * @param v {HTMLEvent} The mouseout event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onItemMouseout = function(v,oSelf) {
+ if(oSelf.prehighlightClassName) {
+ oSelf._togglePrehighlight(this,"mouseout");
+ }
+ else {
+ oSelf._toggleHighlight(this,"from");
+ }
+
+ oSelf.itemMouseOutEvent.fire(oSelf, this);
+};
+
+/**
+ * Handles <li> element click events in the container.
+ *
+ * @method _onItemMouseclick
+ * @param v {HTMLEvent} The click event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onItemMouseclick = function(v,oSelf) {
+ // In case item has not been moused over
+ oSelf._toggleHighlight(this,"to");
+ oSelf._selectItem(this);
+};
+
+/**
+ * Handles container mouseover events.
+ *
+ * @method _onContainerMouseover
+ * @param v {HTMLEvent} The mouseover event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onContainerMouseover = function(v,oSelf) {
+ oSelf._bOverContainer = true;
+};
+
+/**
+ * Handles container mouseout events.
+ *
+ * @method _onContainerMouseout
+ * @param v {HTMLEvent} The mouseout event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onContainerMouseout = function(v,oSelf) {
+ oSelf._bOverContainer = false;
+ // If container is still active
+ if(oSelf._oCurItem) {
+ oSelf._toggleHighlight(oSelf._oCurItem,"to");
+ }
+};
+
+/**
+ * Handles container scroll events.
+ *
+ * @method _onContainerScroll
+ * @param v {HTMLEvent} The scroll event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onContainerScroll = function(v,oSelf) {
+ oSelf._elTextbox.focus();
+};
+
+/**
+ * Handles container resize events.
+ *
+ * @method _onContainerResize
+ * @param v {HTMLEvent} The resize event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onContainerResize = function(v,oSelf) {
+ oSelf._toggleContainerHelpers(oSelf._bContainerOpen);
+};
+
+
+/**
+ * Handles textbox keydown events of functional keys, mainly for UI behavior.
+ *
+ * @method _onTextboxKeyDown
+ * @param v {HTMLEvent} The keydown event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown = function(v,oSelf) {
+ var nKeyCode = v.keyCode;
+
+ switch (nKeyCode) {
+ case 9: // tab
+ if((navigator.userAgent.toLowerCase().indexOf("mac") == -1)) {
+ // select an item or clear out
+ if(oSelf._oCurItem) {
+ if(oSelf.delimChar && (oSelf._nKeyCode != nKeyCode)) {
+ if(oSelf._bContainerOpen) {
+ YAHOO.util.Event.stopEvent(v);
+ }
+ }
+ oSelf._selectItem(oSelf._oCurItem);
+ }
+ else {
+ oSelf._toggleContainer(false);
+ }
+ }
+ break;
+ case 13: // enter
+ if((navigator.userAgent.toLowerCase().indexOf("mac") == -1)) {
+ if(oSelf._oCurItem) {
+ if(oSelf._nKeyCode != nKeyCode) {
+ if(oSelf._bContainerOpen) {
+ YAHOO.util.Event.stopEvent(v);
+ }
+ }
+ oSelf._selectItem(oSelf._oCurItem);
+ }
+ else {
+ oSelf._toggleContainer(false);
+ }
+ }
+ break;
+ case 27: // esc
+ oSelf._toggleContainer(false);
+ return;
+ case 39: // right
+ oSelf._jumpSelection();
+ break;
+ case 38: // up
+ YAHOO.util.Event.stopEvent(v);
+ oSelf._moveSelection(nKeyCode);
+ break;
+ case 40: // down
+ YAHOO.util.Event.stopEvent(v);
+ oSelf._moveSelection(nKeyCode);
+ break;
+ default:
+ break;
+ }
+};
+
+/**
+ * Handles textbox keypress events.
+ * @method _onTextboxKeyPress
+ * @param v {HTMLEvent} The keypress event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress = function(v,oSelf) {
+ var nKeyCode = v.keyCode;
+
+ //Expose only to Mac browsers, where stopEvent is ineffective on keydown events (bug 790337)
+ if((navigator.userAgent.toLowerCase().indexOf("mac") != -1)) {
+ switch (nKeyCode) {
+ case 9: // tab
+ // select an item or clear out
+ if(oSelf._oCurItem) {
+ if(oSelf.delimChar && (oSelf._nKeyCode != nKeyCode)) {
+ if(oSelf._bContainerOpen) {
+ YAHOO.util.Event.stopEvent(v);
+ }
+ }
+ oSelf._selectItem(oSelf._oCurItem);
+ }
+ else {
+ oSelf._toggleContainer(false);
+ }
+ break;
+ case 13: // enter
+ if(oSelf._oCurItem) {
+ if(oSelf._nKeyCode != nKeyCode) {
+ if(oSelf._bContainerOpen) {
+ YAHOO.util.Event.stopEvent(v);
+ }
+ }
+ oSelf._selectItem(oSelf._oCurItem);
+ }
+ else {
+ oSelf._toggleContainer(false);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ //TODO: (?) limit only to non-IE, non-Mac-FF for Korean IME support (bug 811948)
+ // Korean IME detected
+ else if(nKeyCode == 229) {
+ oSelf._queryInterval = setInterval(function() { oSelf._onIMEDetected(oSelf); },500);
+ }
+};
+
+/**
+ * Handles textbox keyup events that trigger queries.
+ *
+ * @method _onTextboxKeyUp
+ * @param v {HTMLEvent} The keyup event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp = function(v,oSelf) {
+ // Check to see if any of the public properties have been updated
+ oSelf._initProps();
+
+ var nKeyCode = v.keyCode;
+
+ oSelf._nKeyCode = nKeyCode;
+ var sText = this.value; //string in textbox
+
+ // Filter out chars that don't trigger queries
+ if(oSelf._isIgnoreKey(nKeyCode) || (sText.toLowerCase() == oSelf._sCurQuery)) {
+ return;
+ }
+ else {
+ oSelf._bItemSelected = false;
+ YAHOO.util.Dom.removeClass(oSelf._oCurItem, oSelf.highlightClassName);
+ oSelf._oCurItem = null;
+
+ oSelf.textboxKeyEvent.fire(oSelf, nKeyCode);
+ }
+
+ // Set timeout on the request
+ if(oSelf.queryDelay > 0) {
+ var nDelayID =
+ setTimeout(function(){oSelf._sendQuery(sText);},(oSelf.queryDelay * 1000));
+
+ if(oSelf._nDelayID != -1) {
+ clearTimeout(oSelf._nDelayID);
+ }
+
+ oSelf._nDelayID = nDelayID;
+ }
+ else {
+ // No delay so send request immediately
+ oSelf._sendQuery(sText);
+ }
+};
+
+/**
+ * Handles text input box receiving focus.
+ *
+ * @method _onTextboxFocus
+ * @param v {HTMLEvent} The focus event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onTextboxFocus = function (v,oSelf) {
+ oSelf._elTextbox.setAttribute("autocomplete","off");
+ oSelf._bFocused = true;
+ if(!oSelf._bItemSelected) {
+ oSelf.textboxFocusEvent.fire(oSelf);
+ }
+};
+
+/**
+ * Handles text input box losing focus.
+ *
+ * @method _onTextboxBlur
+ * @param v {HTMLEvent} The focus event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onTextboxBlur = function (v,oSelf) {
+ // Don't treat as a blur if it was a selection via mouse click
+ if(!oSelf._bOverContainer || (oSelf._nKeyCode == 9)) {
+ // Current query needs to be validated as a selection
+ if(!oSelf._bItemSelected) {
+ var oMatch = oSelf._textMatchesOption();
+ // Container is closed or current query doesn't match any result
+ if(!oSelf._bContainerOpen || (oSelf._bContainerOpen && (oMatch === null))) {
+ // Force selection is enabled so clear the current query
+ if(oSelf.forceSelection) {
+ oSelf._clearSelection();
+ }
+ // Treat current query as a valid selection
+ else {
+ oSelf.unmatchedItemSelectEvent.fire(oSelf);
+ }
+ }
+ // Container is open and current query matches a result
+ else {
+ // Force a selection when textbox is blurred with a match
+ if(oSelf.forceSelection) {
+ oSelf._selectItem(oMatch);
+ }
+ }
+ }
+
+ if(oSelf._bContainerOpen) {
+ oSelf._toggleContainer(false);
+ }
+ oSelf._cancelIntervalDetection(oSelf);
+ oSelf._bFocused = false;
+ oSelf.textboxBlurEvent.fire(oSelf);
+ }
+};
+
+/**
+ * Handles window unload event.
+ *
+ * @method _onWindowUnload
+ * @param v {HTMLEvent} The unload event.
+ * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
+ * @private
+ */
+YAHOO.widget.AutoComplete.prototype._onWindowUnload = function(v,oSelf) {
+ if(oSelf && oSelf._elTextbox && oSelf.allowBrowserAutocomplete) {
+ oSelf._elTextbox.setAttribute("autocomplete","on");
+ }
+};
+
+/****************************************************************************/
+/****************************************************************************/
+/****************************************************************************/
+
+/**
+ * The DataSource classes manages sending a request and returning response from a live
+ * database. Supported data include local JavaScript arrays and objects and databases
+ * accessible via XHR connections. Supported response formats include JavaScript arrays,
+ * JSON, XML, and flat-file textual data.
+ *
+ * @class DataSource
+ * @constructor
+ */
+YAHOO.widget.DataSource = function() {
+ /* abstract class */
+};
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public constants
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Error message for null data responses.
+ *
+ * @property ERROR_DATANULL
+ * @type String
+ * @static
+ * @final
+ */
+YAHOO.widget.DataSource.ERROR_DATANULL = "Response data was null";
+
+/**
+ * Error message for data responses with parsing errors.
+ *
+ * @property ERROR_DATAPARSE
+ * @type String
+ * @static
+ * @final
+ */
+YAHOO.widget.DataSource.ERROR_DATAPARSE = "Response data could not be parsed";
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Max size of the local cache. Set to 0 to turn off caching. Caching is
+ * useful to reduce the number of server connections. Recommended only for data
+ * sources that return comprehensive results for queries or when stale data is
+ * not an issue.
+ *
+ * @property maxCacheEntries
+ * @type Number
+ * @default 15
+ */
+YAHOO.widget.DataSource.prototype.maxCacheEntries = 15;
+
+/**
+ * Use this to fine-tune the matching algorithm used against JS Array types of
+ * DataSource and DataSource caches. If queryMatchContains is true, then the JS
+ * Array or cache returns results that "contain" the query string. By default,
+ * queryMatchContains is set to false, so that only results that "start with"
+ * the query string are returned.
+ *
+ * @property queryMatchContains
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.DataSource.prototype.queryMatchContains = false;
+
+/**
+ * Enables query subset matching. If caching is on and queryMatchSubset is
+ * true, substrings of queries will return matching cached results. For
+ * instance, if the first query is for "abc" susequent queries that start with
+ * "abc", like "abcd", will be queried against the cache, and not the live data
+ * source. Recommended only for DataSources that return comprehensive results
+ * for queries with very few characters.
+ *
+ * @property queryMatchSubset
+ * @type Boolean
+ * @default false
+ *
+ */
+YAHOO.widget.DataSource.prototype.queryMatchSubset = false;
+
+/**
+ * Enables case-sensitivity in the matching algorithm used against JS Array
+ * types of DataSources and DataSource caches. If queryMatchCase is true, only
+ * case-sensitive matches will return.
+ *
+ * @property queryMatchCase
+ * @type Boolean
+ * @default false
+ */
+YAHOO.widget.DataSource.prototype.queryMatchCase = false;
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Public accessor to the unique name of the DataSource instance.
+ *
+ * @method toString
+ * @return {String} Unique name of the DataSource instance
+ */
+YAHOO.widget.DataSource.prototype.toString = function() {
+ return "DataSource " + this._sName;
+};
+
+/**
+ * Retrieves query results, first checking the local cache, then making the
+ * query request to the live data source as defined by the function doQuery.
+ *
+ * @method getResults
+ * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
+ * @param sQuery {String} Query string.
+ * @param oParent {Object} The object instance that has requested data.
+ */
+YAHOO.widget.DataSource.prototype.getResults = function(oCallbackFn, sQuery, oParent) {
+
+ // First look in cache
+ var aResults = this._doQueryCache(oCallbackFn,sQuery,oParent);
+ // Not in cache, so get results from server
+ if(aResults.length === 0) {
+ this.queryEvent.fire(this, oParent, sQuery);
+ this.doQuery(oCallbackFn, sQuery, oParent);
+ }
+};
+
+/**
+ * Abstract method implemented by subclasses to make a query to the live data
+ * source. Must call the callback function with the response returned from the
+ * query. Populates cache (if enabled).
+ *
+ * @method doQuery
+ * @param oCallbackFn {HTMLFunction} Callback function implemented by oParent to which to return results.
+ * @param sQuery {String} Query string.
+ * @param oParent {Object} The object instance that has requested data.
+ */
+YAHOO.widget.DataSource.prototype.doQuery = function(oCallbackFn, sQuery, oParent) {
+ /* override this */
+};
+
+/**
+ * Flushes cache.
+ *
+ * @method flushCache
+ */
+YAHOO.widget.DataSource.prototype.flushCache = function() {
+ if(this._aCache) {
+ this._aCache = [];
+ }
+ if(this._aCacheHelper) {
+ this._aCacheHelper = [];
+ }
+ this.cacheFlushEvent.fire(this);
+
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public events
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Fired when a query is made to the live data source.
+ *
+ * @event queryEvent
+ * @param oSelf {Object} The DataSource instance.
+ * @param oParent {Object} The requesting object.
+ * @param sQuery {String} The query string.
+ */
+YAHOO.widget.DataSource.prototype.queryEvent = null;
+
+/**
+ * Fired when a query is made to the local cache.
+ *
+ * @event cacheQueryEvent
+ * @param oSelf {Object} The DataSource instance.
+ * @param oParent {Object} The requesting object.
+ * @param sQuery {String} The query string.
+ */
+YAHOO.widget.DataSource.prototype.cacheQueryEvent = null;
+
+/**
+ * Fired when data is retrieved from the live data source.
+ *
+ * @event getResultsEvent
+ * @param oSelf {Object} The DataSource instance.
+ * @param oParent {Object} The requesting object.
+ * @param sQuery {String} The query string.
+ * @param aResults {Object[]} Array of result objects.
+ */
+YAHOO.widget.DataSource.prototype.getResultsEvent = null;
+
+/**
+ * Fired when data is retrieved from the local cache.
+ *
+ * @event getCachedResultsEvent
+ * @param oSelf {Object} The DataSource instance.
+ * @param oParent {Object} The requesting object.
+ * @param sQuery {String} The query string.
+ * @param aResults {Object[]} Array of result objects.
+ */
+YAHOO.widget.DataSource.prototype.getCachedResultsEvent = null;
+
+/**
+ * Fired when an error is encountered with the live data source.
+ *
+ * @event dataErrorEvent
+ * @param oSelf {Object} The DataSource instance.
+ * @param oParent {Object} The requesting object.
+ * @param sQuery {String} The query string.
+ * @param sMsg {String} Error message string
+ */
+YAHOO.widget.DataSource.prototype.dataErrorEvent = null;
+
+/**
+ * Fired when the local cache is flushed.
+ *
+ * @event cacheFlushEvent
+ * @param oSelf {Object} The DataSource instance
+ */
+YAHOO.widget.DataSource.prototype.cacheFlushEvent = null;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Internal class variable to index multiple DataSource instances.
+ *
+ * @property _nIndex
+ * @type Number
+ * @private
+ * @static
+ */
+YAHOO.widget.DataSource._nIndex = 0;
+
+/**
+ * Name of DataSource instance.
+ *
+ * @property _sName
+ * @type String
+ * @private
+ */
+YAHOO.widget.DataSource.prototype._sName = null;
+
+/**
+ * Local cache of data result objects indexed chronologically.
+ *
+ * @property _aCache
+ * @type Object[]
+ * @private
+ */
+YAHOO.widget.DataSource.prototype._aCache = null;
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Initializes DataSource instance.
+ *
+ * @method _init
+ * @private
+ */
+YAHOO.widget.DataSource.prototype._init = function() {
+ // Validate and initialize public configs
+ var maxCacheEntries = this.maxCacheEntries;
+ if(!YAHOO.lang.isNumber(maxCacheEntries) || (maxCacheEntries < 0)) {
+ maxCacheEntries = 0;
+ }
+ // Initialize local cache
+ if(maxCacheEntries > 0 && !this._aCache) {
+ this._aCache = [];
+ }
+
+ this._sName = "instance" + YAHOO.widget.DataSource._nIndex;
+ YAHOO.widget.DataSource._nIndex++;
+
+ this.queryEvent = new YAHOO.util.CustomEvent("query", this);
+ this.cacheQueryEvent = new YAHOO.util.CustomEvent("cacheQuery", this);
+ this.getResultsEvent = new YAHOO.util.CustomEvent("getResults", this);
+ this.getCachedResultsEvent = new YAHOO.util.CustomEvent("getCachedResults", this);
+ this.dataErrorEvent = new YAHOO.util.CustomEvent("dataError", this);
+ this.cacheFlushEvent = new YAHOO.util.CustomEvent("cacheFlush", this);
+};
+
+/**
+ * Adds a result object to the local cache, evicting the oldest element if the
+ * cache is full. Newer items will have higher indexes, the oldest item will have
+ * index of 0.
+ *
+ * @method _addCacheElem
+ * @param oResult {Object} Data result object, including array of results.
+ * @private
+ */
+YAHOO.widget.DataSource.prototype._addCacheElem = function(oResult) {
+ var aCache = this._aCache;
+ // Don't add if anything important is missing.
+ if(!aCache || !oResult || !oResult.query || !oResult.results) {
+ return;
+ }
+
+ // If the cache is full, make room by removing from index=0
+ if(aCache.length >= this.maxCacheEntries) {
+ aCache.shift();
+ }
+
+ // Add to cache, at the end of the array
+ aCache.push(oResult);
+};
+
+/**
+ * Queries the local cache for results. If query has been cached, the callback
+ * function is called with the results, and the cached is refreshed so that it
+ * is now the newest element.
+ *
+ * @method _doQueryCache
+ * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
+ * @param sQuery {String} Query string.
+ * @param oParent {Object} The object instance that has requested data.
+ * @return aResults {Object[]} Array of results from local cache if found, otherwise null.
+ * @private
+ */
+YAHOO.widget.DataSource.prototype._doQueryCache = function(oCallbackFn, sQuery, oParent) {
+ var aResults = [];
+ var bMatchFound = false;
+ var aCache = this._aCache;
+ var nCacheLength = (aCache) ? aCache.length : 0;
+ var bMatchContains = this.queryMatchContains;
+ var sOrigQuery;
+
+ // If cache is enabled...
+ if((this.maxCacheEntries > 0) && aCache && (nCacheLength > 0)) {
+ this.cacheQueryEvent.fire(this, oParent, sQuery);
+ // If case is unimportant, normalize query now instead of in loops
+ if(!this.queryMatchCase) {
+ sOrigQuery = sQuery;
+ sQuery = sQuery.toLowerCase();
+ }
+
+ // Loop through each cached element's query property...
+ for(var i = nCacheLength-1; i >= 0; i--) {
+ var resultObj = aCache[i];
+ var aAllResultItems = resultObj.results;
+ // If case is unimportant, normalize match key for comparison
+ var matchKey = (!this.queryMatchCase) ?
+ encodeURIComponent(resultObj.query).toLowerCase():
+ encodeURIComponent(resultObj.query);
+
+ // If a cached match key exactly matches the query...
+ if(matchKey == sQuery) {
+ // Stash all result objects into aResult[] and stop looping through the cache.
+ bMatchFound = true;
+ aResults = aAllResultItems;
+
+ // The matching cache element was not the most recent,
+ // so now we need to refresh the cache.
+ if(i != nCacheLength-1) {
+ // Remove element from its original location
+ aCache.splice(i,1);
+ // Add element as newest
+ this._addCacheElem(resultObj);
+ }
+ break;
+ }
+ // Else if this query is not an exact match and subset matching is enabled...
+ else if(this.queryMatchSubset) {
+ // Loop through substrings of each cached element's query property...
+ for(var j = sQuery.length-1; j >= 0 ; j--) {
+ var subQuery = sQuery.substr(0,j);
+
+ // If a substring of a cached sQuery exactly matches the query...
+ if(matchKey == subQuery) {
+ bMatchFound = true;
+
+ // Go through each cached result object to match against the query...
+ for(var k = aAllResultItems.length-1; k >= 0; k--) {
+ var aRecord = aAllResultItems[k];
+ var sKeyIndex = (this.queryMatchCase) ?
+ encodeURIComponent(aRecord[0]).indexOf(sQuery):
+ encodeURIComponent(aRecord[0]).toLowerCase().indexOf(sQuery);
+
+ // A STARTSWITH match is when the query is found at the beginning of the key string...
+ if((!bMatchContains && (sKeyIndex === 0)) ||
+ // A CONTAINS match is when the query is found anywhere within the key string...
+ (bMatchContains && (sKeyIndex > -1))) {
+ // Stash a match into aResults[].
+ aResults.unshift(aRecord);
+ }
+ }
+
+ // Add the subset match result set object as the newest element to cache,
+ // and stop looping through the cache.
+ resultObj = {};
+ resultObj.query = sQuery;
+ resultObj.results = aResults;
+ this._addCacheElem(resultObj);
+ break;
+ }
+ }
+ if(bMatchFound) {
+ break;
+ }
+ }
+ }
+
+ // If there was a match, send along the results.
+ if(bMatchFound) {
+ this.getCachedResultsEvent.fire(this, oParent, sOrigQuery, aResults);
+ oCallbackFn(sOrigQuery, aResults, oParent);
+ }
+ }
+ return aResults;
+};
+
+
+/****************************************************************************/
+/****************************************************************************/
+/****************************************************************************/
+
+/**
+ * Implementation of YAHOO.widget.DataSource using XML HTTP requests that return
+ * query results.
+ *
+ * @class DS_XHR
+ * @extends YAHOO.widget.DataSource
+ * @requires connection
+ * @constructor
+ * @param sScriptURI {String} Absolute or relative URI to script that returns query
+ * results as JSON, XML, or delimited flat-file data.
+ * @param aSchema {String[]} Data schema definition of results.
+ * @param oConfigs {Object} (optional) Object literal of config params.
+ */
+YAHOO.widget.DS_XHR = function(sScriptURI, aSchema, oConfigs) {
+ // Set any config params passed in to override defaults
+ if(oConfigs && (oConfigs.constructor == Object)) {
+ for(var sConfig in oConfigs) {
+ this[sConfig] = oConfigs[sConfig];
+ }
+ }
+
+ // Initialization sequence
+ if(!YAHOO.lang.isArray(aSchema) || !YAHOO.lang.isString(sScriptURI)) {
+ return;
+ }
+
+ this.schema = aSchema;
+ this.scriptURI = sScriptURI;
+
+ this._init();
+};
+
+YAHOO.widget.DS_XHR.prototype = new YAHOO.widget.DataSource();
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public constants
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * JSON data type.
+ *
+ * @property TYPE_JSON
+ * @type Number
+ * @static
+ * @final
+ */
+YAHOO.widget.DS_XHR.TYPE_JSON = 0;
+
+/**
+ * XML data type.
+ *
+ * @property TYPE_XML
+ * @type Number
+ * @static
+ * @final
+ */
+YAHOO.widget.DS_XHR.TYPE_XML = 1;
+
+/**
+ * Flat-file data type.
+ *
+ * @property TYPE_FLAT
+ * @type Number
+ * @static
+ * @final
+ */
+YAHOO.widget.DS_XHR.TYPE_FLAT = 2;
+
+/**
+ * Error message for XHR failure.
+ *
+ * @property ERROR_DATAXHR
+ * @type String
+ * @static
+ * @final
+ */
+YAHOO.widget.DS_XHR.ERROR_DATAXHR = "XHR response failed";
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Alias to YUI Connection Manager, to allow implementers to customize the utility.
+ *
+ * @property connMgr
+ * @type Object
+ * @default YAHOO.util.Connect
+ */
+YAHOO.widget.DS_XHR.prototype.connMgr = YAHOO.util.Connect;
+
+/**
+ * Number of milliseconds the XHR connection will wait for a server response. A
+ * a value of zero indicates the XHR connection will wait forever. Any value
+ * greater than zero will use the Connection utility's Auto-Abort feature.
+ *
+ * @property connTimeout
+ * @type Number
+ * @default 0
+ */
+YAHOO.widget.DS_XHR.prototype.connTimeout = 0;
+
+/**
+ * Absolute or relative URI to script that returns query results. For instance,
+ * queries will be sent to <scriptURI>?<scriptQueryParam>=userinput
+ *
+ * @property scriptURI
+ * @type String
+ */
+YAHOO.widget.DS_XHR.prototype.scriptURI = null;
+
+/**
+ * Query string parameter name sent to scriptURI. For instance, queries will be
+ * sent to <scriptURI>?<scriptQueryParam>=userinput
+ *
+ * @property scriptQueryParam
+ * @type String
+ * @default "query"
+ */
+YAHOO.widget.DS_XHR.prototype.scriptQueryParam = "query";
+
+/**
+ * String of key/value pairs to append to requests made to scriptURI. Define
+ * this string when you want to send additional query parameters to your script.
+ * When defined, queries will be sent to
+ * <scriptURI>?<scriptQueryParam>=userinput&<scriptQueryAppend>
+ *
+ * @property scriptQueryAppend
+ * @type String
+ * @default ""
+ */
+YAHOO.widget.DS_XHR.prototype.scriptQueryAppend = "";
+
+/**
+ * XHR response data type. Other types that may be defined are YAHOO.widget.DS_XHR.TYPE_XML
+ * and YAHOO.widget.DS_XHR.TYPE_FLAT.
+ *
+ * @property responseType
+ * @type String
+ * @default YAHOO.widget.DS_XHR.TYPE_JSON
+ */
+YAHOO.widget.DS_XHR.prototype.responseType = YAHOO.widget.DS_XHR.TYPE_JSON;
+
+/**
+ * String after which to strip results. If the results from the XHR are sent
+ * back as HTML, the gzip HTML comment appears at the end of the data and should
+ * be ignored.
+ *
+ * @property responseStripAfter
+ * @type String
+ * @default "\n<!-"
+ */
+YAHOO.widget.DS_XHR.prototype.responseStripAfter = "\n 0) {
+ sUri += "&" + this.scriptQueryAppend;
+ }
+ var oResponse = null;
+
+ var oSelf = this;
+ /*
+ * Sets up ajax request callback
+ *
+ * @param {object} oReq HTTPXMLRequest object
+ * @private
+ */
+ var responseSuccess = function(oResp) {
+ // Response ID does not match last made request ID.
+ if(!oSelf._oConn || (oResp.tId != oSelf._oConn.tId)) {
+ oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATANULL);
+ return;
+ }
+//DEBUG
+for(var foo in oResp) {
+}
+ if(!isXML) {
+ oResp = oResp.responseText;
+ }
+ else {
+ oResp = oResp.responseXML;
+ }
+ if(oResp === null) {
+ oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATANULL);
+ return;
+ }
+
+ var aResults = oSelf.parseResponse(sQuery, oResp, oParent);
+ var resultObj = {};
+ resultObj.query = decodeURIComponent(sQuery);
+ resultObj.results = aResults;
+ if(aResults === null) {
+ oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATAPARSE);
+ aResults = [];
+ }
+ else {
+ oSelf.getResultsEvent.fire(oSelf, oParent, sQuery, aResults);
+ oSelf._addCacheElem(resultObj);
+ }
+ oCallbackFn(sQuery, aResults, oParent);
+ };
+
+ var responseFailure = function(oResp) {
+ oSelf.dataErrorEvent.fire(oSelf, oParent, sQuery, YAHOO.widget.DS_XHR.ERROR_DATAXHR);
+ return;
+ };
+
+ var oCallback = {
+ success:responseSuccess,
+ failure:responseFailure
+ };
+
+ if(YAHOO.lang.isNumber(this.connTimeout) && (this.connTimeout > 0)) {
+ oCallback.timeout = this.connTimeout;
+ }
+
+ if(this._oConn) {
+ this.connMgr.abort(this._oConn);
+ }
+
+ oSelf._oConn = this.connMgr.asyncRequest("GET", sUri, oCallback, null);
+};
+
+/**
+ * Parses raw response data into an array of result objects. The result data key
+ * is always stashed in the [0] element of each result object.
+ *
+ * @method parseResponse
+ * @param sQuery {String} Query string.
+ * @param oResponse {Object} The raw response data to parse.
+ * @param oParent {Object} The object instance that has requested data.
+ * @returns {Object[]} Array of result objects.
+ */
+YAHOO.widget.DS_XHR.prototype.parseResponse = function(sQuery, oResponse, oParent) {
+ var aSchema = this.schema;
+ var aResults = [];
+ var bError = false;
+
+ // Strip out comment at the end of results
+ var nEnd = ((this.responseStripAfter !== "") && (oResponse.indexOf)) ?
+ oResponse.indexOf(this.responseStripAfter) : -1;
+ if(nEnd != -1) {
+ oResponse = oResponse.substring(0,nEnd);
+ }
+
+ switch (this.responseType) {
+ case YAHOO.widget.DS_XHR.TYPE_JSON:
+ var jsonList, jsonObjParsed;
+ // Check for YUI JSON
+ if(YAHOO.lang.JSON) {
+ // Use the JSON utility if available
+ jsonObjParsed = YAHOO.lang.JSON.parse(oResponse);
+ if(!jsonObjParsed) {
+ bError = true;
+ break;
+ }
+ else {
+ try {
+ // eval is necessary here since aSchema[0] is of unknown depth
+ jsonList = eval("jsonObjParsed." + aSchema[0]);
+ }
+ catch(e) {
+ bError = true;
+ break;
+ }
+ }
+ }
+ // Check for JSON lib
+ else if(oResponse.parseJSON) {
+ // Use the new JSON utility if available
+ jsonObjParsed = oResponse.parseJSON();
+ if(!jsonObjParsed) {
+ bError = true;
+ }
+ else {
+ try {
+ // eval is necessary here since aSchema[0] is of unknown depth
+ jsonList = eval("jsonObjParsed." + aSchema[0]);
+ }
+ catch(e) {
+ bError = true;
+ break;
+ }
+ }
+ }
+ // Use older JSON lib if available
+ else if(window.JSON) {
+ jsonObjParsed = JSON.parse(oResponse);
+ if(!jsonObjParsed) {
+ bError = true;
+ break;
+ }
+ else {
+ try {
+ // eval is necessary here since aSchema[0] is of unknown depth
+ jsonList = eval("jsonObjParsed." + aSchema[0]);
+ }
+ catch(e) {
+ bError = true;
+ break;
+ }
+ }
+ }
+ else {
+ // Parse the JSON response as a string
+ try {
+ // Trim leading spaces
+ while (oResponse.substring(0,1) == " ") {
+ oResponse = oResponse.substring(1, oResponse.length);
+ }
+
+ // Invalid JSON response
+ if(oResponse.indexOf("{") < 0) {
+ bError = true;
+ break;
+ }
+
+ // Empty (but not invalid) JSON response
+ if(oResponse.indexOf("{}") === 0) {
+ break;
+ }
+
+ // Turn the string into an object literal...
+ // ...eval is necessary here
+ var jsonObjRaw = eval("(" + oResponse + ")");
+ if(!jsonObjRaw) {
+ bError = true;
+ break;
+ }
+
+ // Grab the object member that contains an array of all reponses...
+ // ...eval is necessary here since aSchema[0] is of unknown depth
+ jsonList = eval("(jsonObjRaw." + aSchema[0]+")");
+ }
+ catch(e) {
+ bError = true;
+ break;
+ }
+ }
+
+ if(!jsonList) {
+ bError = true;
+ break;
+ }
+
+ if(!YAHOO.lang.isArray(jsonList)) {
+ jsonList = [jsonList];
+ }
+
+ // Loop through the array of all responses...
+ for(var i = jsonList.length-1; i >= 0 ; i--) {
+ var aResultItem = [];
+ var jsonResult = jsonList[i];
+ // ...and loop through each data field value of each response
+ for(var j = aSchema.length-1; j >= 1 ; j--) {
+ // ...and capture data into an array mapped according to the schema...
+ var dataFieldValue = jsonResult[aSchema[j]];
+ if(!dataFieldValue) {
+ dataFieldValue = "";
+ }
+ aResultItem.unshift(dataFieldValue);
+ }
+ // If schema isn't well defined, pass along the entire result object
+ if(aResultItem.length == 1) {
+ aResultItem.push(jsonResult);
+ }
+ // Capture the array of data field values in an array of results
+ aResults.unshift(aResultItem);
+ }
+ break;
+ case YAHOO.widget.DS_XHR.TYPE_XML:
+ // Get the collection of results
+ var xmlList = oResponse.getElementsByTagName(aSchema[0]);
+ if(!xmlList) {
+ bError = true;
+ break;
+ }
+ // Loop through each result
+ for(var k = xmlList.length-1; k >= 0 ; k--) {
+ var result = xmlList.item(k);
+ var aFieldSet = [];
+ // Loop through each data field in each result using the schema
+ for(var m = aSchema.length-1; m >= 1 ; m--) {
+ var sValue = null;
+ // Values may be held in an attribute...
+ var xmlAttr = result.attributes.getNamedItem(aSchema[m]);
+ if(xmlAttr) {
+ sValue = xmlAttr.value;
+ }
+ // ...or in a node
+ else{
+ var xmlNode = result.getElementsByTagName(aSchema[m]);
+ if(xmlNode && xmlNode.item(0) && xmlNode.item(0).firstChild) {
+ sValue = xmlNode.item(0).firstChild.nodeValue;
+ }
+ else {
+ sValue = "";
+ }
+ }
+ // Capture the schema-mapped data field values into an array
+ aFieldSet.unshift(sValue);
+ }
+ // Capture each array of values into an array of results
+ aResults.unshift(aFieldSet);
+ }
+ break;
+ case YAHOO.widget.DS_XHR.TYPE_FLAT:
+ if(oResponse.length > 0) {
+ // Delete the last line delimiter at the end of the data if it exists
+ var newLength = oResponse.length-aSchema[0].length;
+ if(oResponse.substr(newLength) == aSchema[0]) {
+ oResponse = oResponse.substr(0, newLength);
+ }
+ if(oResponse.length > 0) {
+ var aRecords = oResponse.split(aSchema[0]);
+ for(var n = aRecords.length-1; n >= 0; n--) {
+ if(aRecords[n].length > 0) {
+ aResults[n] = aRecords[n].split(aSchema[1]);
+ }
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ sQuery = null;
+ oResponse = null;
+ oParent = null;
+ if(bError) {
+ return null;
+ }
+ else {
+ return aResults;
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * XHR connection object.
+ *
+ * @property _oConn
+ * @type Object
+ * @private
+ */
+YAHOO.widget.DS_XHR.prototype._oConn = null;
+
+
+/****************************************************************************/
+/****************************************************************************/
+/****************************************************************************/
+
+/**
+ * Implementation of YAHOO.widget.DataSource using the Get Utility to generate
+ * dynamic SCRIPT nodes for data retrieval.
+ *
+ * @class DS_ScriptNode
+ * @constructor
+ * @extends YAHOO.widget.DataSource
+ * @param sUri {String} URI to the script location that will return data.
+ * @param aSchema {String[]} Data schema definition of results.
+ * @param oConfigs {Object} (optional) Object literal of config params.
+ */
+YAHOO.widget.DS_ScriptNode = function(sUri, aSchema, oConfigs) {
+ // Set any config params passed in to override defaults
+ if(oConfigs && (oConfigs.constructor == Object)) {
+ for(var sConfig in oConfigs) {
+ this[sConfig] = oConfigs[sConfig];
+ }
+ }
+
+ // Initialization sequence
+ if(!YAHOO.lang.isArray(aSchema) || !YAHOO.lang.isString(sUri)) {
+ return;
+ }
+
+ this.schema = aSchema;
+ this.scriptURI = sUri;
+
+ this._init();
+};
+
+YAHOO.widget.DS_ScriptNode.prototype = new YAHOO.widget.DataSource();
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Alias to YUI Get Utility. Allows implementers to specify their own
+ * subclasses of the YUI Get Utility.
+ *
+ * @property getUtility
+ * @type Object
+ * @default YAHOO.util.Get
+ */
+YAHOO.widget.DS_ScriptNode.prototype.getUtility = YAHOO.util.Get;
+
+/**
+ * URI to the script that returns data.
+ *
+ * @property scriptURI
+ * @type String
+ */
+YAHOO.widget.DS_ScriptNode.prototype.scriptURI = null;
+
+/**
+ * Query string parameter name sent to scriptURI. For instance, requests will be
+ * sent to <scriptURI>?<scriptQueryParam>=queryString
+ *
+ * @property scriptQueryParam
+ * @type String
+ * @default "query"
+ */
+YAHOO.widget.DS_ScriptNode.prototype.scriptQueryParam = "query";
+
+/**
+ * Defines request/response management in the following manner:
+ *
+ *
+ *
ignoreStaleResponses
+ *
Send all requests, but handle only the response for the most recently sent request.
+ *
allowAll
+ *
Send all requests and handle all responses.
+ *
+ *
+ * @property asyncMode
+ * @type String
+ * @default "allowAll"
+ */
+YAHOO.widget.DS_ScriptNode.prototype.asyncMode = "allowAll";
+
+/**
+ * Callback string parameter name sent to scriptURI. For instance, requests will be
+ * sent to <scriptURI>?<scriptCallbackParam>=callbackFunction
+ *
+ * @property scriptCallbackParam
+ * @type String
+ * @default "callback"
+ */
+YAHOO.widget.DS_ScriptNode.prototype.scriptCallbackParam = "callback";
+
+/**
+ * Global array of callback functions, one for each request sent.
+ *
+ * @property callbacks
+ * @type Function[]
+ * @static
+ */
+YAHOO.widget.DS_ScriptNode.callbacks = [];
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Unique ID to track requests.
+ *
+ * @property _nId
+ * @type Number
+ * @private
+ * @static
+ */
+YAHOO.widget.DS_ScriptNode._nId = 0;
+
+/**
+ * Counter for pending requests. When this is 0, it is safe to purge callbacks
+ * array.
+ *
+ * @property _nPending
+ * @type Number
+ * @private
+ * @static
+ */
+YAHOO.widget.DS_ScriptNode._nPending = 0;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Queries the live data source. Results are passed back to a callback function.
+ *
+ * @method doQuery
+ * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
+ * @param sQuery {String} Query string.
+ * @param oParent {Object} The object instance that has requested data.
+ */
+YAHOO.widget.DS_ScriptNode.prototype.doQuery = function(oCallbackFn, sQuery, oParent) {
+ var oSelf = this;
+
+ // If there are no global pending requests, it is safe to purge global callback stack and global counter
+ if(YAHOO.widget.DS_ScriptNode._nPending === 0) {
+ YAHOO.widget.DS_ScriptNode.callbacks = [];
+ YAHOO.widget.DS_ScriptNode._nId = 0;
+ }
+
+ // ID for this request
+ var id = YAHOO.widget.DS_ScriptNode._nId;
+ YAHOO.widget.DS_ScriptNode._nId++;
+
+ // Dynamically add handler function with a closure to the callback stack
+ YAHOO.widget.DS_ScriptNode.callbacks[id] = function(oResponse) {
+ if((oSelf.asyncMode !== "ignoreStaleResponses")||
+ (id === YAHOO.widget.DS_ScriptNode.callbacks.length-1)) { // Must ignore stale responses
+ oSelf.handleResponse(oResponse, oCallbackFn, sQuery, oParent);
+ }
+ else {
+ }
+
+ delete YAHOO.widget.DS_ScriptNode.callbacks[id];
+ };
+
+ // We are now creating a request
+ YAHOO.widget.DS_ScriptNode._nPending++;
+
+ var sUri = this.scriptURI+"&"+ this.scriptQueryParam+"="+sQuery+"&"+
+ this.scriptCallbackParam+"=YAHOO.widget.DS_ScriptNode.callbacks["+id+"]";
+ this.getUtility.script(sUri,
+ {autopurge:true,
+ onsuccess:YAHOO.widget.DS_ScriptNode._bumpPendingDown,
+ onfail:YAHOO.widget.DS_ScriptNode._bumpPendingDown});
+};
+
+/**
+ * Parses JSON response data into an array of result objects and passes it to
+ * the callback function.
+ *
+ * @method handleResponse
+ * @param oResponse {Object} The raw response data to parse.
+ * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
+ * @param sQuery {String} Query string.
+ * @param oParent {Object} The object instance that has requested data.
+ */
+YAHOO.widget.DS_ScriptNode.prototype.handleResponse = function(oResponse, oCallbackFn, sQuery, oParent) {
+ var aSchema = this.schema;
+ var aResults = [];
+ var bError = false;
+
+ var jsonList, jsonObjParsed;
+
+ // Parse the JSON response as a string
+ try {
+ // Grab the object member that contains an array of all reponses...
+ // ...eval is necessary here since aSchema[0] is of unknown depth
+ jsonList = eval("(oResponse." + aSchema[0]+")");
+ }
+ catch(e) {
+ bError = true;
+ }
+
+ if(!jsonList) {
+ bError = true;
+ jsonList = [];
+ }
+
+ else if(!YAHOO.lang.isArray(jsonList)) {
+ jsonList = [jsonList];
+ }
+
+ // Loop through the array of all responses...
+ for(var i = jsonList.length-1; i >= 0 ; i--) {
+ var aResultItem = [];
+ var jsonResult = jsonList[i];
+ // ...and loop through each data field value of each response
+ for(var j = aSchema.length-1; j >= 1 ; j--) {
+ // ...and capture data into an array mapped according to the schema...
+ var dataFieldValue = jsonResult[aSchema[j]];
+ if(!dataFieldValue) {
+ dataFieldValue = "";
+ }
+ aResultItem.unshift(dataFieldValue);
+ }
+ // If schema isn't well defined, pass along the entire result object
+ if(aResultItem.length == 1) {
+ aResultItem.push(jsonResult);
+ }
+ // Capture the array of data field values in an array of results
+ aResults.unshift(aResultItem);
+ }
+
+ if(bError) {
+ aResults = null;
+ }
+
+ if(aResults === null) {
+ this.dataErrorEvent.fire(this, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATAPARSE);
+ aResults = [];
+ }
+ else {
+ var resultObj = {};
+ resultObj.query = decodeURIComponent(sQuery);
+ resultObj.results = aResults;
+ this._addCacheElem(resultObj);
+
+ this.getResultsEvent.fire(this, oParent, sQuery, aResults);
+ }
+
+ oCallbackFn(sQuery, aResults, oParent);
+};
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Private methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Any success/failure response should decrement counter.
+ *
+ * @method _bumpPendingDown
+ * @private
+ */
+YAHOO.widget.DS_ScriptNode._bumpPendingDown = function() {
+ YAHOO.widget.DS_ScriptNode._nPending--;
+};
+
+
+/****************************************************************************/
+/****************************************************************************/
+/****************************************************************************/
+
+/**
+ * Implementation of YAHOO.widget.DataSource using a native Javascript function as
+ * its live data source.
+ *
+ * @class DS_JSFunction
+ * @constructor
+ * @extends YAHOO.widget.DataSource
+ * @param oFunction {HTMLFunction} In-memory Javascript function that returns query results as an array of objects.
+ * @param oConfigs {Object} (optional) Object literal of config params.
+ */
+YAHOO.widget.DS_JSFunction = function(oFunction, oConfigs) {
+ // Set any config params passed in to override defaults
+ if(oConfigs && (oConfigs.constructor == Object)) {
+ for(var sConfig in oConfigs) {
+ this[sConfig] = oConfigs[sConfig];
+ }
+ }
+
+ // Initialization sequence
+ if(!YAHOO.lang.isFunction(oFunction)) {
+ return;
+ }
+ else {
+ this.dataFunction = oFunction;
+ this._init();
+ }
+};
+
+YAHOO.widget.DS_JSFunction.prototype = new YAHOO.widget.DataSource();
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * In-memory Javascript function that returns query results.
+ *
+ * @property dataFunction
+ * @type HTMLFunction
+ */
+YAHOO.widget.DS_JSFunction.prototype.dataFunction = null;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Queries the live data source defined by function for results. Results are
+ * passed back to a callback function.
+ *
+ * @method doQuery
+ * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
+ * @param sQuery {String} Query string.
+ * @param oParent {Object} The object instance that has requested data.
+ */
+YAHOO.widget.DS_JSFunction.prototype.doQuery = function(oCallbackFn, sQuery, oParent) {
+ var oFunction = this.dataFunction;
+ var aResults = [];
+
+ aResults = oFunction(sQuery);
+ if(aResults === null) {
+ this.dataErrorEvent.fire(this, oParent, sQuery, YAHOO.widget.DataSource.ERROR_DATANULL);
+ return;
+ }
+
+ var resultObj = {};
+ resultObj.query = decodeURIComponent(sQuery);
+ resultObj.results = aResults;
+ this._addCacheElem(resultObj);
+
+ this.getResultsEvent.fire(this, oParent, sQuery, aResults);
+ oCallbackFn(sQuery, aResults, oParent);
+ return;
+};
+
+
+/****************************************************************************/
+/****************************************************************************/
+/****************************************************************************/
+
+/**
+ * Implementation of YAHOO.widget.DataSource using a native Javascript array as
+ * its live data source.
+ *
+ * @class DS_JSArray
+ * @constructor
+ * @extends YAHOO.widget.DataSource
+ * @param aData {String[]} In-memory Javascript array of simple string data.
+ * @param oConfigs {Object} (optional) Object literal of config params.
+ */
+YAHOO.widget.DS_JSArray = function(aData, oConfigs) {
+ // Set any config params passed in to override defaults
+ if(oConfigs && (oConfigs.constructor == Object)) {
+ for(var sConfig in oConfigs) {
+ this[sConfig] = oConfigs[sConfig];
+ }
+ }
+
+ // Initialization sequence
+ if(!YAHOO.lang.isArray(aData)) {
+ return;
+ }
+ else {
+ this.data = aData;
+ this._init();
+ }
+};
+
+YAHOO.widget.DS_JSArray.prototype = new YAHOO.widget.DataSource();
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public member variables
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * In-memory Javascript array of strings.
+ *
+ * @property data
+ * @type Array
+ */
+YAHOO.widget.DS_JSArray.prototype.data = null;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Public methods
+//
+/////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Queries the live data source defined by data for results. Results are passed
+ * back to a callback function.
+ *
+ * @method doQuery
+ * @param oCallbackFn {HTMLFunction} Callback function defined by oParent object to which to return results.
+ * @param sQuery {String} Query string.
+ * @param oParent {Object} The object instance that has requested data.
+ */
+YAHOO.widget.DS_JSArray.prototype.doQuery = function(oCallbackFn, sQuery, oParent) {
+ var i;
+ var aData = this.data; // the array
+ var aResults = []; // container for results
+ var bMatchFound = false;
+ var bMatchContains = this.queryMatchContains;
+ if(sQuery) {
+ if(!this.queryMatchCase) {
+ sQuery = sQuery.toLowerCase();
+ }
+
+ // Loop through each element of the array...
+ // which can be a string or an array of strings
+ for(i = aData.length-1; i >= 0; i--) {
+ var aDataset = [];
+
+ if(YAHOO.lang.isString(aData[i])) {
+ aDataset[0] = aData[i];
+ }
+ else if(YAHOO.lang.isArray(aData[i])) {
+ aDataset = aData[i];
+ }
+
+ if(YAHOO.lang.isString(aDataset[0])) {
+ var sKeyIndex = (this.queryMatchCase) ?
+ encodeURIComponent(aDataset[0]).indexOf(sQuery):
+ encodeURIComponent(aDataset[0]).toLowerCase().indexOf(sQuery);
+
+ // A STARTSWITH match is when the query is found at the beginning of the key string...
+ if((!bMatchContains && (sKeyIndex === 0)) ||
+ // A CONTAINS match is when the query is found anywhere within the key string...
+ (bMatchContains && (sKeyIndex > -1))) {
+ // Stash a match into aResults[].
+ aResults.unshift(aDataset);
+ }
+ }
+ }
+ }
+ else {
+ for(i = aData.length-1; i >= 0; i--) {
+ if(YAHOO.lang.isString(aData[i])) {
+ aResults.unshift([aData[i]]);
+ }
+ else if(YAHOO.lang.isArray(aData[i])) {
+ aResults.unshift(aData[i]);
+ }
+ }
+ }
+
+ this.getResultsEvent.fire(this, oParent, sQuery, aResults);
+ oCallbackFn(sQuery, aResults, oParent);
+};
+
+YAHOO.register("autocomplete", YAHOO.widget.AutoComplete, {version: "2.5.2", build: "1076"});
diff --git a/lib/yui/base/README b/lib/yui/base/README
index a5f3562d1f..469f490baf 100755
--- a/lib/yui/base/README
+++ b/lib/yui/base/README
@@ -1,5 +1,13 @@
YUI Library - Base - Release Notes
+Version 2.5.2
+
+ * No changes.
+
+Version 2.5.1
+
+ * No changes.
+
Version 2.5.0
* No changes.
diff --git a/lib/yui/base/base-min.css b/lib/yui/base/base-min.css
index fcb5b2c853..5e7424e857 100755
--- a/lib/yui/base/base-min.css
+++ b/lib/yui/base/base-min.css
@@ -2,6 +2,6 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
h1{font-size:138.5%;}h2{font-size:123.1%;}h3{font-size:108%;}h1,h2,h3{margin:1em 0;}h1,h2,h3,h4,h5,h6,strong{font-weight:bold;}abbr,acronym{border-bottom:1px dotted #000;cursor:help;} em{font-style:italic;}blockquote,ul,ol,dl{margin:1em;}ol,ul,dl{margin-left:2em;}ol li{list-style:decimal outside;}ul li{list-style:disc outside;}dl dd{margin-left:1em;}th,td{border:1px solid #000;padding:.5em;}th{font-weight:bold;text-align:center;}caption{margin-bottom:.5em;text-align:center;}p,fieldset,table,pre{margin-bottom:1em;}input[type=text],input[type=password],textarea{width:12.25em;*width:11.9em;}
diff --git a/lib/yui/base/base.css b/lib/yui/base/base.css
index bb504f8873..5267e405b3 100755
--- a/lib/yui/base/base.css
+++ b/lib/yui/base/base.css
@@ -2,7 +2,7 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
/* base.css, part of YUI's CSS Foundation */
h1 {
diff --git a/lib/yui/button/README b/lib/yui/button/README
index b0dca1d4a4..746ad5da48 100755
--- a/lib/yui/button/README
+++ b/lib/yui/button/README
@@ -1,3 +1,24 @@
+*** Version 2.5.2 ***
+
+Fixed the following bugs:
+-------------------------
+
++ Button instances no longer flicker in Firefox 3 when their "label" attributed is updated.
+
++ Scrolled Menus of Buttons whose type attribute is set to "menu" or "split" no longer appear
+ on top of their corresponding Button instance.
+
++ The keyboard shortcut responsible for triggering the display of the Menu for Button instances of
+ type "split" will no longer trigger the display of the browser's default context menu in Opera.
+
+
+
+*** Version 2.5.1 ***
+
++ No changes.
+
+
+
*** Version 2.5.0 ***
+ Fixed issue where returning false inside the scope of a listener for attribute "before"
diff --git a/lib/yui/button/assets/button-core.css b/lib/yui/button/assets/button-core.css
index e3ff7ea186..18c3aba6aa 100755
--- a/lib/yui/button/assets/button-core.css
+++ b/lib/yui/button/assets/button-core.css
@@ -2,7 +2,7 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
.yui-button {
diff --git a/lib/yui/button/assets/skins/sam/button-skin.css b/lib/yui/button/assets/skins/sam/button-skin.css
index ad97242ac3..d66210980c 100755
--- a/lib/yui/button/assets/skins/sam/button-skin.css
+++ b/lib/yui/button/assets/skins/sam/button-skin.css
@@ -2,7 +2,7 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
.yui-skin-sam .yui-button {
diff --git a/lib/yui/button/assets/skins/sam/button.css b/lib/yui/button/assets/skins/sam/button.css
index cef04bbe92..1f31b93985 100755
--- a/lib/yui/button/assets/skins/sam/button.css
+++ b/lib/yui/button/assets/skins/sam/button.css
@@ -2,6 +2,6 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
.yui-button{display:-moz-inline-box;display:inline-block;vertical-align:text-bottom;}.yui-button .first-child{display:block;*display:inline-block;}.yui-button button,.yui-button a{display:block;*display:inline-block;border:none;margin:0;}.yui-button button{background-color:transparent;*overflow:visible;cursor:pointer;}.yui-button a{text-decoration:none;}.yui-skin-sam .yui-button{border-width:1px 0;border-style:solid;border-color:#808080;background:url(../../../../assets/skins/sam/sprite.png) repeat-x 0 0;margin:auto .25em;}.yui-skin-sam .yui-button .first-child{border-width:0 1px;border-style:solid;border-color:#808080;margin:0 -1px;*position:relative;*left:-1px;}.yui-skin-sam .yui-button button,.yui-skin-sam .yui-button a{padding:0 10px;font-size:93%;line-height:2;*line-height:1.7;min-height:2em;*min-height:auto;color:#000;}.yui-skin-sam .yui-button a{*line-height:2;}.yui-skin-sam .yui-split-button button,.yui-skin-sam .yui-menu-button button{padding-right:20px;background-position:right center;background-repeat:no-repeat;}.yui-skin-sam .yui-menu-button button{background-image:url(menu-button-arrow.png);}.yui-skin-sam .yui-split-button button{background-image:url(split-button-arrow.png);}.yui-skin-sam .yui-button-focus{border-color:#7D98B8;background-position:0 -1300px;}.yui-skin-sam .yui-button-focus .first-child{border-color:#7D98B8;}.yui-skin-sam .yui-button-focus button,.yui-skin-sam .yui-button-focus a{color:#000;}.yui-skin-sam .yui-split-button-focus button{background-image:url(split-button-arrow-focus.png);}.yui-skin-sam .yui-button-hover{border-color:#7D98B8;background-position:0 -1300px;}.yui-skin-sam .yui-button-hover .first-child{border-color:#7D98B8;}.yui-skin-sam .yui-button-hover button,.yui-skin-sam .yui-button-hover a{color:#000;}.yui-skin-sam .yui-split-button-hover button{background-image:url(split-button-arrow-hover.png);}.yui-skin-sam .yui-button-active{border-color:#7D98B8;background-position:0 -1700px;}.yui-skin-sam .yui-button-active .first-child{border-color:#7D98B8;}.yui-skin-sam .yui-button-active button,.yui-skin-sam .yui-button-active a{color:#000;}.yui-skin-sam .yui-split-button-activeoption{border-color:#808080;background-position:0 0;}.yui-skin-sam .yui-split-button-activeoption .first-child{border-color:#808080;}.yui-skin-sam .yui-split-button-activeoption button{background-image:url(split-button-arrow-active.png);}.yui-skin-sam .yui-radio-button-checked,.yui-skin-sam .yui-checkbox-button-checked{border-color:#304369;background-position:0 -1400px;}.yui-skin-sam .yui-radio-button-checked .first-child,.yui-skin-sam .yui-checkbox-button-checked .first-child{border-color:#304369;}.yui-skin-sam .yui-radio-button-checked button,.yui-skin-sam .yui-checkbox-button-checked button{color:#fff;}.yui-skin-sam .yui-button-disabled{border-color:#ccc;background-position:0 -1500px;}.yui-skin-sam .yui-button-disabled .first-child{border-color:#ccc;}.yui-skin-sam .yui-button-disabled button,.yui-skin-sam .yui-button-disabled a{color:#A6A6A6;cursor:default;}.yui-skin-sam .yui-menu-button-disabled button{background-image:url(menu-button-arrow-disabled.png);}.yui-skin-sam .yui-split-button-disabled button{background-image:url(split-button-arrow-disabled.png);}
diff --git a/lib/yui/button/button-debug.js b/lib/yui/button/button-debug.js
index 2474c910f6..3ba21016cf 100644
--- a/lib/yui/button/button-debug.js
+++ b/lib/yui/button/button-debug.js
@@ -2,7 +2,7 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
/**
* @module button
@@ -80,6 +80,7 @@ version: 2.5.0
var Dom = YAHOO.util.Dom,
Event = YAHOO.util.Event,
Lang = YAHOO.lang,
+ UA = YAHOO.env.ua,
Overlay = YAHOO.widget.Overlay,
Menu = YAHOO.widget.Menu,
@@ -120,7 +121,7 @@ version: 2.5.0
if (Lang.isString(p_sType) && Lang.isString(p_sName)) {
- if (YAHOO.env.ua.ie) {
+ if (UA.ie) {
/*
For IE it is necessary to create the element with the
@@ -834,6 +835,7 @@ version: 2.5.0
_setLabel: function (p_sLabel) {
this._button.innerHTML = p_sLabel;
+
/*
Remove and add the default class name from the root element
@@ -846,21 +848,17 @@ version: 2.5.0
*/
var sClass,
- me;
+ nGeckoVersion = UA.gecko;
+
- if (YAHOO.env.ua.gecko && Dom.inDocument(this.get("element"))) {
+ if (nGeckoVersion && nGeckoVersion < 1.9 && Dom.inDocument(this.get("element"))) {
- me = this;
sClass = this.CSS_CLASS_NAME;
this.removeClass(sClass);
- window.setTimeout(function () {
-
- me.addClass(sClass);
-
- }, 0);
-
+ Lang.later(0, this, this.addClass, sClass);
+
}
},
@@ -1437,9 +1435,32 @@ version: 2.5.0
* @return {Boolean}
*/
_isSplitButtonOptionKey: function (p_oEvent) {
+
+ var bShowMenu = (p_oEvent.ctrlKey && p_oEvent.shiftKey &&
+ Event.getCharCode(p_oEvent) == 77);
+
+
+ function onKeyPress(p_oEvent) {
+
+ Event.preventDefault(p_oEvent);
+
+ this.removeListener("keypress", onKeyPress);
+
+ }
+
+
+ /*
+ It is necessary to add a "keypress" event listener to prevent Opera's default
+ browser context menu from appearing when the user presses Ctrl + Shift + M.
+ */
+
+ if (bShowMenu && UA.opera) {
+
+ this.on("keypress", onKeyPress);
+
+ }
- return (p_oEvent.ctrlKey && p_oEvent.shiftKey &&
- Event.getCharCode(p_oEvent) == 77);
+ return bShowMenu;
},
@@ -1612,6 +1633,11 @@ version: 2.5.0
oMenu.align("bl", "tl");
}
+ else {
+
+ oMenu.align("tl", "bl");
+
+ }
}
@@ -1655,12 +1681,12 @@ version: 2.5.0
if (Menu && oMenu && (oMenu instanceof Menu)) {
- oMenu.cfg.applyConfig({ context: [oButtonEL, "tl", "bl"],
- clicktohide: false,
- visible: true });
+ oMenu.cfg.applyConfig({ context: [oButtonEL, "tl", "bl"], clicktohide: false });
oMenu.cfg.fireQueue();
+ oMenu.show();
+
oMenu.cfg.setProperty("maxheight", 0);
oMenu.align("tl", "bl");
@@ -2888,7 +2914,7 @@ version: 2.5.0
}
- if (YAHOO.env.ua.ie) {
+ if (UA.ie) {
bSubmitForm = oForm.fireEvent("onsubmit");
@@ -2910,7 +2936,7 @@ version: 2.5.0
method as well.
*/
- if ((YAHOO.env.ua.ie || YAHOO.env.ua.webkit) && bSubmitForm) {
+ if ((UA.ie || UA.webkit) && bSubmitForm) {
oForm.submit();
@@ -4735,4 +4761,4 @@ version: 2.5.0
});
})();
-YAHOO.register("button", YAHOO.widget.Button, {version: "2.5.0", build: "895"});
+YAHOO.register("button", YAHOO.widget.Button, {version: "2.5.2", build: "1076"});
diff --git a/lib/yui/button/button-min.js b/lib/yui/button/button-min.js
index e1d1b6df8f..f13928ce36 100644
--- a/lib/yui/button/button-min.js
+++ b/lib/yui/button/button-min.js
@@ -2,10 +2,10 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
-(function(){var G=YAHOO.util.Dom,L=YAHOO.util.Event,I=YAHOO.lang,B=YAHOO.widget.Overlay,J=YAHOO.widget.Menu,D={},K=null,E=null,C=null;function F(N,M,Q,O){var R,P;if(I.isString(N)&&I.isString(M)){if(YAHOO.env.ua.ie){P="";R=document.createElement(P);}else{R=document.createElement("input");R.name=M;R.type=N;if(O){R.checked=true;}}R.value=Q;return R;}}function H(N,T){var M=N.nodeName.toUpperCase(),R=this,S,O,P;function U(V){if(!(V in T)){S=N.getAttributeNode(V);if(S&&("value" in S)){T[V]=S.value;}}}function Q(){U("type");if(T.type=="button"){T.type="push";}if(!("disabled" in T)){T.disabled=N.disabled;}U("name");U("value");U("title");}switch(M){case"A":T.type="link";U("href");U("target");break;case"INPUT":Q();if(!("checked" in T)){T.checked=N.checked;}break;case"BUTTON":Q();O=N.parentNode.parentNode;if(G.hasClass(O,this.CSS_CLASS_NAME+"-checked")){T.checked=true;}if(G.hasClass(O,this.CSS_CLASS_NAME+"-disabled")){T.disabled=true;}N.removeAttribute("value");N.setAttribute("type","button");break;}N.removeAttribute("id");N.removeAttribute("name");if(!("tabindex" in T)){T.tabindex=N.tabIndex;}if(!("label" in T)){P=M=="INPUT"?N.value:N.innerHTML;if(P&&P.length>0){T.label=P;}}}function A(O){var N=O.attributes,M=N.srcelement,Q=M.nodeName.toUpperCase(),P=this;if(Q==this.NODE_NAME){O.element=M;O.id=M.id;G.getElementsBy(function(R){switch(R.nodeName.toUpperCase()){case"BUTTON":case"A":case"INPUT":H.call(P,R,N);break;}},"*",M);}else{switch(Q){case"BUTTON":case"A":case"INPUT":H.call(this,M,N);break;}}}YAHOO.widget.Button=function(Q,N){if(!B&&YAHOO.widget.Overlay){B=YAHOO.widget.Overlay;}if(!J&&YAHOO.widget.Menu){J=YAHOO.widget.Menu;}var P=YAHOO.widget.Button.superclass.constructor,O,M;if(arguments.length==1&&!I.isString(Q)&&!Q.nodeName){if(!Q.id){Q.id=G.generateId();}P.call(this,(this.createButtonElement(Q.type)),Q);}else{O={element:null,attributes:(N||{})};if(I.isString(Q)){M=G.get(Q);if(M){if(!O.attributes.id){O.attributes.id=Q;}O.attributes.srcelement=M;A.call(this,O);if(!O.element){O.element=this.createButtonElement(O.attributes.type);}P.call(this,O.element,O.attributes);}}else{if(Q.nodeName){if(!O.attributes.id){if(Q.id){O.attributes.id=Q.id;}else{O.attributes.id=G.generateId();}}O.attributes.srcelement=Q;A.call(this,O);if(!O.element){O.element=this.createButtonElement(O.attributes.type);}P.call(this,O.element,O.attributes);}}}};YAHOO.extend(YAHOO.widget.Button,YAHOO.util.Element,{_button:null,_menu:null,_hiddenFields:null,_onclickAttributeValue:null,_activationKeyPressed:false,_activationButtonPressed:false,_hasKeyEventHandlers:false,_hasMouseEventHandlers:false,NODE_NAME:"SPAN",CHECK_ACTIVATION_KEYS:[32],ACTIVATION_KEYS:[13,32],OPTION_AREA_WIDTH:20,CSS_CLASS_NAME:"yui-button",RADIO_DEFAULT_TITLE:"Unchecked. Click to check.",RADIO_CHECKED_TITLE:"Checked. Click another button to uncheck",CHECKBOX_DEFAULT_TITLE:"Unchecked. Click to check.",CHECKBOX_CHECKED_TITLE:"Checked. Click to uncheck.",MENUBUTTON_DEFAULT_TITLE:"Menu collapsed. Click to expand.",MENUBUTTON_MENU_VISIBLE_TITLE:"Menu expanded. Click or press Esc to collapse.",SPLITBUTTON_DEFAULT_TITLE:("Menu collapsed. Click inside option region or press Ctrl + Shift + M to show the menu."),SPLITBUTTON_OPTION_VISIBLE_TITLE:"Menu expanded. Press Esc or Ctrl + Shift + M to hide the menu.",SUBMIT_TITLE:"Click to submit form.",_setType:function(M){if(M=="split"){this.on("option",this._onOption);}},_setLabel:function(M){this._button.innerHTML=M;var O,N;if(YAHOO.env.ua.gecko&&G.inDocument(this.get("element"))){N=this;O=this.CSS_CLASS_NAME;this.removeClass(O);window.setTimeout(function(){N.addClass(O);},0);}},_setTabIndex:function(M){this._button.tabIndex=M;},_setTitle:function(N){var M=N;if(this.get("type")!="link"){if(!M){switch(this.get("type")){case"radio":M=this.RADIO_DEFAULT_TITLE;break;case"checkbox":M=this.CHECKBOX_DEFAULT_TITLE;break;case"menu":M=this.MENUBUTTON_DEFAULT_TITLE;break;case"split":M=this.SPLITBUTTON_DEFAULT_TITLE;break;case"submit":M=this.SUBMIT_TITLE;break;}}this._button.title=M;}},_setDisabled:function(M){if(this.get("type")!="link"){if(M){if(this._menu){this._menu.hide();}if(this.hasFocus()){this.blur();}this._button.setAttribute("disabled","disabled");this.addStateCSSClasses("disabled");this.removeStateCSSClasses("hover");this.removeStateCSSClasses("active");this.removeStateCSSClasses("focus");}else{this._button.removeAttribute("disabled");this.removeStateCSSClasses("disabled");}}},_setHref:function(M){if(this.get("type")=="link"){this._button.href=M;}},_setTarget:function(M){if(this.get("type")=="link"){this._button.setAttribute("target",M);}},_setChecked:function(N){var O=this.get("type"),M;if(O=="checkbox"||O=="radio"){if(N){this.addStateCSSClasses("checked");M=(O=="radio")?this.RADIO_CHECKED_TITLE:this.CHECKBOX_CHECKED_TITLE;}else{this.removeStateCSSClasses("checked");M=(O=="radio")?this.RADIO_DEFAULT_TITLE:this.CHECKBOX_DEFAULT_TITLE;}this.set("title",M);}},_setMenu:function(W){var Q=this.get("lazyloadmenu"),T=this.get("element"),M,Y=false,Z,P,S,O,N,V,R;if(!B){return false;}if(J){M=J.prototype.CSS_CLASS_NAME;}function X(){Z.render(T.parentNode);this.removeListener("appendTo",X);}function U(){if(Z){G.addClass(Z.element,this.get("menuclassname"));G.addClass(Z.element,"yui-"+this.get("type")+"-button-menu");Z.showEvent.subscribe(this._onMenuShow,null,this);Z.hideEvent.subscribe(this._onMenuHide,null,this);Z.renderEvent.subscribe(this._onMenuRender,null,this);if(J&&Z instanceof J){Z.keyDownEvent.subscribe(this._onMenuKeyDown,this,true);Z.subscribe("click",this._onMenuClick,this,true);Z.itemAddedEvent.subscribe(this._onMenuItemAdded,this,true);S=Z.srcElement;if(S&&S.nodeName.toUpperCase()=="SELECT"){S.style.display="none";S.parentNode.removeChild(S);}}else{if(B&&Z instanceof B){if(!K){K=new YAHOO.widget.OverlayManager();}K.register(Z);}}this._menu=Z;if(!Y){if(Q&&J&&!(Z instanceof J)){Z.beforeShowEvent.subscribe(this._onOverlayBeforeShow,null,this);}else{if(!Q){if(G.inDocument(T)){Z.render(T.parentNode);
-}else{this.on("appendTo",X);}}}}}}if(W&&J&&(W instanceof J)){Z=W;O=Z.getItems();N=O.length;Y=true;if(N>0){R=N-1;do{V=O[R];if(V){V.cfg.subscribeToConfigEvent("selected",this._onMenuItemSelected,V,this);}}while(R--);}U.call(this);}else{if(B&&W&&(W instanceof B)){Z=W;Y=true;Z.cfg.setProperty("visible",false);Z.cfg.setProperty("context",[T,"tl","bl"]);U.call(this);}else{if(J&&I.isArray(W)){this.on("appendTo",function(){Z=new J(G.generateId(),{lazyload:Q,itemdata:W});U.call(this);});}else{if(I.isString(W)){P=G.get(W);if(P){if(J&&G.hasClass(P,M)||P.nodeName.toUpperCase()=="SELECT"){Z=new J(W,{lazyload:Q});U.call(this);}else{if(B){Z=new B(W,{visible:false,context:[T,"tl","bl"]});U.call(this);}}}}else{if(W&&W.nodeName){if(J&&G.hasClass(W,M)||W.nodeName.toUpperCase()=="SELECT"){Z=new J(W,{lazyload:Q});U.call(this);}else{if(B){if(!W.id){G.generateId(W);}Z=new B(W,{visible:false,context:[T,"tl","bl"]});U.call(this);}}}}}}}},_setOnClick:function(M){if(this._onclickAttributeValue&&(this._onclickAttributeValue!=M)){this.removeListener("click",this._onclickAttributeValue.fn);this._onclickAttributeValue=null;}if(!this._onclickAttributeValue&&I.isObject(M)&&I.isFunction(M.fn)){this.on("click",M.fn,M.obj,M.scope);this._onclickAttributeValue=M;}},_setSelectedMenuItem:function(N){var M=this._menu,O;if(J&&M&&M instanceof J){O=M.getItem(N);if(O&&!O.cfg.getProperty("selected")){O.cfg.setProperty("selected",true);}}},_isActivationKey:function(M){var Q=this.get("type"),N=(Q=="checkbox"||Q=="radio")?this.CHECK_ACTIVATION_KEYS:this.ACTIVATION_KEYS,P=N.length,O;if(P>0){O=P-1;do{if(M==N[O]){return true;}}while(O--);}},_isSplitButtonOptionKey:function(M){return(M.ctrlKey&&M.shiftKey&&L.getCharCode(M)==77);},_addListenersToForm:function(){var S=this.getForm(),R=YAHOO.widget.Button.onFormKeyPress,Q,M,P,O,N;if(S){L.on(S,"reset",this._onFormReset,null,this);L.on(S,"submit",this.createHiddenFields,null,this);M=this.get("srcelement");if(this.get("type")=="submit"||(M&&M.type=="submit")){P=L.getListeners(S,"keypress");Q=false;if(P){O=P.length;if(O>0){N=O-1;do{if(P[N].fn==R){Q=true;break;}}while(N--);}}if(!Q){L.on(S,"keypress",R);}}}},_showMenu:function(R){if(YAHOO.widget.MenuManager){YAHOO.widget.MenuManager.hideVisible();}if(K){K.hideAll();}var P=B.VIEWPORT_OFFSET,Y=this._menu,W=this,Z=W.get("element"),T=false,V=G.getY(Z),U=G.getDocumentScrollTop(),M,Q,b;if(U){V=V-U;}var O=V,N=(G.getViewportHeight()-(V+Z.offsetHeight));function S(){if(T){return(O-P);}else{return(N-P);}}function a(){var c=S();if(Q>c){M=Y.cfg.getProperty("minscrollheight");if(c>M){Y.cfg.setProperty("maxheight",c);if(T){Y.align("bl","tl");}}if(c"+(M=="link"?"":"")+""+O+">";return N;},addStateCSSClasses:function(M){var N=this.get("type");if(I.isString(M)){if(M!="activeoption"){this.addClass(this.CSS_CLASS_NAME+("-"+M));}this.addClass("yui-"+N+("-button-"+M));}},removeStateCSSClasses:function(M){var N=this.get("type");if(I.isString(M)){this.removeClass(this.CSS_CLASS_NAME+("-"+M));this.removeClass("yui-"+N+("-button-"+M));}},createHiddenFields:function(){this.removeHiddenFields();var R=this.getForm(),U,N,P,S,T,O,Q,M;if(R&&!this.get("disabled")){N=this.get("type");P=(N=="checkbox"||N=="radio");if(P||(E==this)){U=F((P?N:"hidden"),this.get("name"),this.get("value"),this.get("checked"));if(U){if(P){U.style.display="none";}R.appendChild(U);}}S=this._menu;if(J&&S&&(S instanceof J)){M=S.srcElement;T=this.get("selectedMenuItem");if(T){if(M&&M.nodeName.toUpperCase()=="SELECT"){R.appendChild(M);M.selectedIndex=T.index;}else{Q=(T.value===null||T.value==="")?T.cfg.getProperty("text"):T.value;O=this.get("name");if(Q&&O){M=F("hidden",(O+"_options"),Q);R.appendChild(M);}}}}if(U&&M){this._hiddenFields=[U,M];}else{if(!U&&M){this._hiddenFields=M;}else{if(U&&!M){this._hiddenFields=U;}}}return this._hiddenFields;}},removeHiddenFields:function(){var P=this._hiddenFields,N,O;function M(Q){if(G.inDocument(Q)){Q.parentNode.removeChild(Q);}}if(P){if(I.isArray(P)){N=P.length;if(N>0){O=N-1;do{M(P[O]);}while(O--);}}else{M(P);}this._hiddenFields=null;}},submitForm:function(){var P=this.getForm(),O=this.get("srcelement"),N=false,M;if(P){if(this.get("type")=="submit"||(O&&O.type=="submit")){E=this;}if(YAHOO.env.ua.ie){N=P.fireEvent("onsubmit");}else{M=document.createEvent("HTMLEvents");M.initEvent("submit",true,true);N=P.dispatchEvent(M);}if((YAHOO.env.ua.ie||YAHOO.env.ua.webkit)&&N){P.submit();}}return N;},init:function(M,T){var O=T.type=="link"?"a":"button",Q=T.srcelement,S=M.getElementsByTagName(O)[0],R;if(!S){R=M.getElementsByTagName("input")[0];if(R){S=document.createElement("button");S.setAttribute("type","button");R.parentNode.replaceChild(S,R);}}this._button=S;YAHOO.widget.Button.superclass.init.call(this,M,T);
-D[this.get("id")]=this;this.addClass(this.CSS_CLASS_NAME);this.addClass("yui-"+this.get("type")+"-button");L.on(this._button,"focus",this._onFocus,null,this);this.on("mouseover",this._onMouseOver);this.on("click",this._onClick);this.on("appendTo",this._onAppendTo);var V=this.get("container"),N=this.get("element"),U=G.inDocument(N),P;if(V){if(Q&&Q!=N){P=Q.parentNode;if(P){P.removeChild(Q);}}if(I.isString(V)){L.onContentReady(V,function(){this.appendTo(V);},null,this);}else{this.appendTo(V);}}else{if(!U&&Q&&Q!=N){P=Q.parentNode;if(P){this.fireEvent("beforeAppendTo",{type:"beforeAppendTo",target:P});P.replaceChild(N,Q);this.fireEvent("appendTo",{type:"appendTo",target:P});}}else{if(this.get("type")!="link"&&U&&Q&&Q==N){this._addListenersToForm();}}}},initAttributes:function(N){var M=N||{};YAHOO.widget.Button.superclass.initAttributes.call(this,M);this.setAttributeConfig("type",{value:(M.type||"push"),validator:I.isString,writeOnce:true,method:this._setType});this.setAttributeConfig("label",{value:M.label,validator:I.isString,method:this._setLabel});this.setAttributeConfig("value",{value:M.value});this.setAttributeConfig("name",{value:M.name,validator:I.isString});this.setAttributeConfig("tabindex",{value:M.tabindex,validator:I.isNumber,method:this._setTabIndex});this.configureAttribute("title",{value:M.title,validator:I.isString,method:this._setTitle});this.setAttributeConfig("disabled",{value:(M.disabled||false),validator:I.isBoolean,method:this._setDisabled});this.setAttributeConfig("href",{value:M.href,validator:I.isString,method:this._setHref});this.setAttributeConfig("target",{value:M.target,validator:I.isString,method:this._setTarget});this.setAttributeConfig("checked",{value:(M.checked||false),validator:I.isBoolean,method:this._setChecked});this.setAttributeConfig("container",{value:M.container,writeOnce:true});this.setAttributeConfig("srcelement",{value:M.srcelement,writeOnce:true});this.setAttributeConfig("menu",{value:null,method:this._setMenu,writeOnce:true});this.setAttributeConfig("lazyloadmenu",{value:(M.lazyloadmenu===false?false:true),validator:I.isBoolean,writeOnce:true});this.setAttributeConfig("menuclassname",{value:(M.menuclassname||"yui-button-menu"),validator:I.isString,method:this._setMenuClassName,writeOnce:true});this.setAttributeConfig("selectedMenuItem",{value:null,method:this._setSelectedMenuItem});this.setAttributeConfig("onclick",{value:M.onclick,method:this._setOnClick});this.setAttributeConfig("focusmenu",{value:(M.focusmenu===false?false:true),validator:I.isBoolean});},focus:function(){if(!this.get("disabled")){this._button.focus();}},blur:function(){if(!this.get("disabled")){this._button.blur();}},hasFocus:function(){return(C==this);},isActive:function(){return this.hasClass(this.CSS_CLASS_NAME+"-active");},getMenu:function(){return this._menu;},getForm:function(){return this._button.form;},getHiddenFields:function(){return this._hiddenFields;},destroy:function(){var O=this.get("element"),N=O.parentNode,M=this._menu,Q;if(M){if(K&&K.find(M)){K.remove(M);}M.destroy();}L.purgeElement(O);L.purgeElement(this._button);L.removeListener(document,"mouseup",this._onDocumentMouseUp);L.removeListener(document,"keyup",this._onDocumentKeyUp);L.removeListener(document,"mousedown",this._onDocumentMouseDown);var P=this.getForm();if(P){L.removeListener(P,"reset",this._onFormReset);L.removeListener(P,"submit",this.createHiddenFields);}this.unsubscribeAll();if(N){N.removeChild(O);}delete D[this.get("id")];Q=G.getElementsByClassName(this.CSS_CLASS_NAME,this.NODE_NAME,P);if(I.isArray(Q)&&Q.length===0){L.removeListener(P,"keypress",YAHOO.widget.Button.onFormKeyPress);}},fireEvent:function(N,M){var O=arguments[0];if(this.DOM_EVENTS[O]&&this.get("disabled")){return ;}return YAHOO.widget.Button.superclass.fireEvent.apply(this,arguments);},toString:function(){return("Button "+this.get("id"));}});YAHOO.widget.Button.onFormKeyPress=function(Q){var O=L.getTarget(Q),R=L.getCharCode(Q),P=O.nodeName&&O.nodeName.toUpperCase(),M=O.type,S=false,U,V,N,W;function T(Z){var Y,X;switch(Z.nodeName.toUpperCase()){case"INPUT":case"BUTTON":if(Z.type=="submit"&&!Z.disabled){if(!S&&!N){N=Z;}if(V&&!W){W=Z;}}break;default:Y=Z.id;if(Y){U=D[Y];if(U){S=true;if(!U.get("disabled")){X=U.get("srcelement");if(!V&&(U.get("type")=="submit"||(X&&X.type=="submit"))){V=U;}}}}break;}}if(R==13&&((P=="INPUT"&&(M=="text"||M=="password"||M=="checkbox"||M=="radio"||M=="file"))||P=="SELECT")){G.getElementsBy(T,"*",this);if(N){N.focus();}else{if(!N&&V){if(W){L.preventDefault(Q);}V.submitForm();}}}};YAHOO.widget.Button.addHiddenFieldsToForm=function(M){var R=G.getElementsByClassName(YAHOO.widget.Button.prototype.CSS_CLASS_NAME,"*",M),P=R.length,Q,N,O;if(P>0){for(O=0;O
0){F=H-1;do{this._buttons[F].set("disabled",G);}while(F--);}},_onKeyDown:function(K){var G=B.getTarget(K),I=B.getCharCode(K),H=G.parentNode.parentNode.id,J=E[H],F=-1;if(I==37||I==38){F=(J.index===0)?(this._buttons.length-1):(J.index-1);}else{if(I==39||I==40){F=(J.index===(this._buttons.length-1))?0:(J.index+1);}}if(F>-1){this.check(F);this.getButton(F).focus();
-}},_onAppendTo:function(H){var I=this._buttons,G=I.length,F;for(F=0;F0){this.addButtons(J);}function F(K){return(K.type=="radio");}J=C.getElementsBy(F,"input",this.get("element"));if(J.length>0){this.addButtons(J);}this.on("keydown",this._onKeyDown);this.on("appendTo",this._onAppendTo);var G=this.get("container");if(G){if(D.isString(G)){B.onContentReady(G,function(){this.appendTo(G);},null,this);}else{this.appendTo(G);}}},initAttributes:function(G){var F=G||{};YAHOO.widget.ButtonGroup.superclass.initAttributes.call(this,F);this.setAttributeConfig("name",{value:F.name,validator:D.isString});this.setAttributeConfig("disabled",{value:(F.disabled||false),validator:D.isBoolean,method:this._setDisabled});this.setAttributeConfig("value",{value:F.value});this.setAttributeConfig("container",{value:F.container,writeOnce:true});this.setAttributeConfig("checkedButton",{value:null});},addButton:function(J){var L,K,G,F,H,I;if(J instanceof A&&J.get("type")=="radio"){L=J;}else{if(!D.isString(J)&&!J.nodeName){J.type="radio";L=new A(J);}else{L=new A(J,{type:"radio"});}}if(L){F=this._buttons.length;H=L.get("name");I=this.get("name");L.index=F;this._buttons[F]=L;E[L.get("id")]=L;if(H!=I){L.set("name",I);}if(this.get("disabled")){L.set("disabled",true);}if(L.get("checked")){this.set("checkedButton",L);}K=L.get("element");G=this.get("element");if(K.parentNode!=G){G.appendChild(K);}L.on("checkedChange",this._onButtonCheckedChange,L,this);return L;}},addButtons:function(G){var H,I,J,F;if(D.isArray(G)){H=G.length;J=[];if(H>0){for(F=0;F0){return J;}}}},removeButton:function(H){var I=this.getButton(H),G,F;if(I){this._buttons.splice(H,1);delete E[I.get("id")];I.removeListener("checkedChange",this._onButtonCheckedChange);I.destroy();G=this._buttons.length;if(G>0){F=this._buttons.length-1;do{this._buttons[F].index=F;}while(F--);}}},getButton:function(F){if(D.isNumber(F)){return this._buttons[F];}},getButtons:function(){return this._buttons;},getCount:function(){return this._buttons.length;},focus:function(H){var I,G,F;if(D.isNumber(H)){I=this._buttons[H];if(I){I.focus();}}else{G=this.getCount();for(F=0;F0){G=this._buttons.length-1;do{this._buttons[G].destroy();}while(G--);}B.purgeElement(H);F.removeChild(H);},toString:function(){return("ButtonGroup "+this.get("id"));}});})();YAHOO.register("button",YAHOO.widget.Button,{version:"2.5.0",build:"895"});
\ No newline at end of file
+(function(){var G=YAHOO.util.Dom,M=YAHOO.util.Event,I=YAHOO.lang,L=YAHOO.env.ua,B=YAHOO.widget.Overlay,J=YAHOO.widget.Menu,D={},K=null,E=null,C=null;function F(O,N,R,P){var S,Q;if(I.isString(O)&&I.isString(N)){if(L.ie){Q='";S=document.createElement(Q);}else{S=document.createElement("input");S.name=N;S.type=O;if(P){S.checked=true;}}S.value=R;return S;}}function H(O,U){var N=O.nodeName.toUpperCase(),S=this,T,P,Q;function V(W){if(!(W in U)){T=O.getAttributeNode(W);if(T&&("value" in T)){U[W]=T.value;}}}function R(){V("type");if(U.type=="button"){U.type="push";}if(!("disabled" in U)){U.disabled=O.disabled;}V("name");V("value");V("title");}switch(N){case"A":U.type="link";V("href");V("target");break;case"INPUT":R();if(!("checked" in U)){U.checked=O.checked;}break;case"BUTTON":R();P=O.parentNode.parentNode;if(G.hasClass(P,this.CSS_CLASS_NAME+"-checked")){U.checked=true;}if(G.hasClass(P,this.CSS_CLASS_NAME+"-disabled")){U.disabled=true;}O.removeAttribute("value");O.setAttribute("type","button");break;}O.removeAttribute("id");O.removeAttribute("name");if(!("tabindex" in U)){U.tabindex=O.tabIndex;}if(!("label" in U)){Q=N=="INPUT"?O.value:O.innerHTML;if(Q&&Q.length>0){U.label=Q;}}}function A(P){var O=P.attributes,N=O.srcelement,R=N.nodeName.toUpperCase(),Q=this;if(R==this.NODE_NAME){P.element=N;P.id=N.id;G.getElementsBy(function(S){switch(S.nodeName.toUpperCase()){case"BUTTON":case"A":case"INPUT":H.call(Q,S,O);break;}},"*",N);}else{switch(R){case"BUTTON":case"A":case"INPUT":H.call(this,N,O);break;}}}YAHOO.widget.Button=function(R,O){if(!B&&YAHOO.widget.Overlay){B=YAHOO.widget.Overlay;}if(!J&&YAHOO.widget.Menu){J=YAHOO.widget.Menu;}var Q=YAHOO.widget.Button.superclass.constructor,P,N;if(arguments.length==1&&!I.isString(R)&&!R.nodeName){if(!R.id){R.id=G.generateId();}Q.call(this,(this.createButtonElement(R.type)),R);}else{P={element:null,attributes:(O||{})};if(I.isString(R)){N=G.get(R);if(N){if(!P.attributes.id){P.attributes.id=R;}P.attributes.srcelement=N;A.call(this,P);if(!P.element){P.element=this.createButtonElement(P.attributes.type);}Q.call(this,P.element,P.attributes);}}else{if(R.nodeName){if(!P.attributes.id){if(R.id){P.attributes.id=R.id;}else{P.attributes.id=G.generateId();}}P.attributes.srcelement=R;A.call(this,P);if(!P.element){P.element=this.createButtonElement(P.attributes.type);}Q.call(this,P.element,P.attributes);}}}};YAHOO.extend(YAHOO.widget.Button,YAHOO.util.Element,{_button:null,_menu:null,_hiddenFields:null,_onclickAttributeValue:null,_activationKeyPressed:false,_activationButtonPressed:false,_hasKeyEventHandlers:false,_hasMouseEventHandlers:false,NODE_NAME:"SPAN",CHECK_ACTIVATION_KEYS:[32],ACTIVATION_KEYS:[13,32],OPTION_AREA_WIDTH:20,CSS_CLASS_NAME:"yui-button",RADIO_DEFAULT_TITLE:"Unchecked. Click to check.",RADIO_CHECKED_TITLE:"Checked. Click another button to uncheck",CHECKBOX_DEFAULT_TITLE:"Unchecked. Click to check.",CHECKBOX_CHECKED_TITLE:"Checked. Click to uncheck.",MENUBUTTON_DEFAULT_TITLE:"Menu collapsed. Click to expand.",MENUBUTTON_MENU_VISIBLE_TITLE:"Menu expanded. Click or press Esc to collapse.",SPLITBUTTON_DEFAULT_TITLE:("Menu collapsed. Click inside option "+"region or press Ctrl + Shift + M to show the menu."),SPLITBUTTON_OPTION_VISIBLE_TITLE:"Menu expanded. Press Esc or Ctrl + Shift + M to hide the menu.",SUBMIT_TITLE:"Click to submit form.",_setType:function(N){if(N=="split"){this.on("option",this._onOption);}},_setLabel:function(O){this._button.innerHTML=O;var P,N=L.gecko;if(N&&N<1.9&&G.inDocument(this.get("element"))){P=this.CSS_CLASS_NAME;this.removeClass(P);I.later(0,this,this.addClass,P);}},_setTabIndex:function(N){this._button.tabIndex=N;},_setTitle:function(O){var N=O;if(this.get("type")!="link"){if(!N){switch(this.get("type")){case"radio":N=this.RADIO_DEFAULT_TITLE;break;case"checkbox":N=this.CHECKBOX_DEFAULT_TITLE;break;case"menu":N=this.MENUBUTTON_DEFAULT_TITLE;break;case"split":N=this.SPLITBUTTON_DEFAULT_TITLE;break;case"submit":N=this.SUBMIT_TITLE;break;}}this._button.title=N;}},_setDisabled:function(N){if(this.get("type")!="link"){if(N){if(this._menu){this._menu.hide();}if(this.hasFocus()){this.blur();}this._button.setAttribute("disabled","disabled");this.addStateCSSClasses("disabled");this.removeStateCSSClasses("hover");this.removeStateCSSClasses("active");this.removeStateCSSClasses("focus");}else{this._button.removeAttribute("disabled");this.removeStateCSSClasses("disabled");}}},_setHref:function(N){if(this.get("type")=="link"){this._button.href=N;}},_setTarget:function(N){if(this.get("type")=="link"){this._button.setAttribute("target",N);}},_setChecked:function(O){var P=this.get("type"),N;if(P=="checkbox"||P=="radio"){if(O){this.addStateCSSClasses("checked");N=(P=="radio")?this.RADIO_CHECKED_TITLE:this.CHECKBOX_CHECKED_TITLE;}else{this.removeStateCSSClasses("checked");N=(P=="radio")?this.RADIO_DEFAULT_TITLE:this.CHECKBOX_DEFAULT_TITLE;}this.set("title",N);}},_setMenu:function(X){var R=this.get("lazyloadmenu"),U=this.get("element"),N,Z=false,a,Q,T,P,O,W,S;if(!B){return false;}if(J){N=J.prototype.CSS_CLASS_NAME;}function Y(){a.render(U.parentNode);this.removeListener("appendTo",Y);}function V(){if(a){G.addClass(a.element,this.get("menuclassname"));G.addClass(a.element,"yui-"+this.get("type")+"-button-menu");a.showEvent.subscribe(this._onMenuShow,null,this);a.hideEvent.subscribe(this._onMenuHide,null,this);a.renderEvent.subscribe(this._onMenuRender,null,this);if(J&&a instanceof J){a.keyDownEvent.subscribe(this._onMenuKeyDown,this,true);a.subscribe("click",this._onMenuClick,this,true);a.itemAddedEvent.subscribe(this._onMenuItemAdded,this,true);T=a.srcElement;if(T&&T.nodeName.toUpperCase()=="SELECT"){T.style.display="none";T.parentNode.removeChild(T);}}else{if(B&&a instanceof B){if(!K){K=new YAHOO.widget.OverlayManager();}K.register(a);}}this._menu=a;if(!Z){if(R&&J&&!(a instanceof J)){a.beforeShowEvent.subscribe(this._onOverlayBeforeShow,null,this);}else{if(!R){if(G.inDocument(U)){a.render(U.parentNode);
+}else{this.on("appendTo",Y);}}}}}}if(X&&J&&(X instanceof J)){a=X;P=a.getItems();O=P.length;Z=true;if(O>0){S=O-1;do{W=P[S];if(W){W.cfg.subscribeToConfigEvent("selected",this._onMenuItemSelected,W,this);}}while(S--);}V.call(this);}else{if(B&&X&&(X instanceof B)){a=X;Z=true;a.cfg.setProperty("visible",false);a.cfg.setProperty("context",[U,"tl","bl"]);V.call(this);}else{if(J&&I.isArray(X)){this.on("appendTo",function(){a=new J(G.generateId(),{lazyload:R,itemdata:X});V.call(this);});}else{if(I.isString(X)){Q=G.get(X);if(Q){if(J&&G.hasClass(Q,N)||Q.nodeName.toUpperCase()=="SELECT"){a=new J(X,{lazyload:R});V.call(this);}else{if(B){a=new B(X,{visible:false,context:[U,"tl","bl"]});V.call(this);}}}}else{if(X&&X.nodeName){if(J&&G.hasClass(X,N)||X.nodeName.toUpperCase()=="SELECT"){a=new J(X,{lazyload:R});V.call(this);}else{if(B){if(!X.id){G.generateId(X);}a=new B(X,{visible:false,context:[U,"tl","bl"]});V.call(this);}}}}}}}},_setOnClick:function(N){if(this._onclickAttributeValue&&(this._onclickAttributeValue!=N)){this.removeListener("click",this._onclickAttributeValue.fn);this._onclickAttributeValue=null;}if(!this._onclickAttributeValue&&I.isObject(N)&&I.isFunction(N.fn)){this.on("click",N.fn,N.obj,N.scope);this._onclickAttributeValue=N;}},_setSelectedMenuItem:function(O){var N=this._menu,P;if(J&&N&&N instanceof J){P=N.getItem(O);if(P&&!P.cfg.getProperty("selected")){P.cfg.setProperty("selected",true);}}},_isActivationKey:function(N){var R=this.get("type"),O=(R=="checkbox"||R=="radio")?this.CHECK_ACTIVATION_KEYS:this.ACTIVATION_KEYS,Q=O.length,P;if(Q>0){P=Q-1;do{if(N==O[P]){return true;}}while(P--);}},_isSplitButtonOptionKey:function(P){var O=(P.ctrlKey&&P.shiftKey&&M.getCharCode(P)==77);function N(Q){M.preventDefault(Q);this.removeListener("keypress",N);}if(O&&L.opera){this.on("keypress",N);}return O;},_addListenersToForm:function(){var T=this.getForm(),S=YAHOO.widget.Button.onFormKeyPress,R,N,Q,P,O;if(T){M.on(T,"reset",this._onFormReset,null,this);M.on(T,"submit",this.createHiddenFields,null,this);N=this.get("srcelement");if(this.get("type")=="submit"||(N&&N.type=="submit")){Q=M.getListeners(T,"keypress");R=false;if(Q){P=Q.length;if(P>0){O=P-1;do{if(Q[O].fn==S){R=true;break;}}while(O--);}}if(!R){M.on(T,"keypress",S);}}}},_showMenu:function(S){if(YAHOO.widget.MenuManager){YAHOO.widget.MenuManager.hideVisible();}if(K){K.hideAll();}var Q=B.VIEWPORT_OFFSET,Z=this._menu,X=this,a=X.get("element"),U=false,W=G.getY(a),V=G.getDocumentScrollTop(),N,R,c;if(V){W=W-V;}var P=W,O=(G.getViewportHeight()-(W+a.offsetHeight));function T(){if(U){return(P-Q);}else{return(O-Q);}}function b(){var d=T();if(R>d){N=Z.cfg.getProperty("minscrollheight");if(d>N){Z.cfg.setProperty("maxheight",d);if(U){Z.align("bl","tl");}else{Z.align("tl","bl");}}if(d'+(N=="link"?"":'')+""+P+">";return O;},addStateCSSClasses:function(N){var O=this.get("type");if(I.isString(N)){if(N!="activeoption"){this.addClass(this.CSS_CLASS_NAME+("-"+N));}this.addClass("yui-"+O+("-button-"+N));}},removeStateCSSClasses:function(N){var O=this.get("type");if(I.isString(N)){this.removeClass(this.CSS_CLASS_NAME+("-"+N));this.removeClass("yui-"+O+("-button-"+N));}},createHiddenFields:function(){this.removeHiddenFields();var S=this.getForm(),V,O,Q,T,U,P,R,N;if(S&&!this.get("disabled")){O=this.get("type");Q=(O=="checkbox"||O=="radio");if(Q||(E==this)){V=F((Q?O:"hidden"),this.get("name"),this.get("value"),this.get("checked"));if(V){if(Q){V.style.display="none";}S.appendChild(V);}}T=this._menu;if(J&&T&&(T instanceof J)){N=T.srcElement;U=this.get("selectedMenuItem");if(U){if(N&&N.nodeName.toUpperCase()=="SELECT"){S.appendChild(N);N.selectedIndex=U.index;}else{R=(U.value===null||U.value==="")?U.cfg.getProperty("text"):U.value;P=this.get("name");if(R&&P){N=F("hidden",(P+"_options"),R);S.appendChild(N);}}}}if(V&&N){this._hiddenFields=[V,N];}else{if(!V&&N){this._hiddenFields=N;}else{if(V&&!N){this._hiddenFields=V;}}}return this._hiddenFields;}},removeHiddenFields:function(){var Q=this._hiddenFields,O,P;function N(R){if(G.inDocument(R)){R.parentNode.removeChild(R);}}if(Q){if(I.isArray(Q)){O=Q.length;if(O>0){P=O-1;do{N(Q[P]);}while(P--);}}else{N(Q);}this._hiddenFields=null;}},submitForm:function(){var Q=this.getForm(),P=this.get("srcelement"),O=false,N;if(Q){if(this.get("type")=="submit"||(P&&P.type=="submit")){E=this;}if(L.ie){O=Q.fireEvent("onsubmit");}else{N=document.createEvent("HTMLEvents");N.initEvent("submit",true,true);O=Q.dispatchEvent(N);}if((L.ie||L.webkit)&&O){Q.submit();}}return O;},init:function(N,U){var P=U.type=="link"?"a":"button",R=U.srcelement,T=N.getElementsByTagName(P)[0],S;if(!T){S=N.getElementsByTagName("input")[0];if(S){T=document.createElement("button");
+T.setAttribute("type","button");S.parentNode.replaceChild(T,S);}}this._button=T;YAHOO.widget.Button.superclass.init.call(this,N,U);D[this.get("id")]=this;this.addClass(this.CSS_CLASS_NAME);this.addClass("yui-"+this.get("type")+"-button");M.on(this._button,"focus",this._onFocus,null,this);this.on("mouseover",this._onMouseOver);this.on("click",this._onClick);this.on("appendTo",this._onAppendTo);var W=this.get("container"),O=this.get("element"),V=G.inDocument(O),Q;if(W){if(R&&R!=O){Q=R.parentNode;if(Q){Q.removeChild(R);}}if(I.isString(W)){M.onContentReady(W,function(){this.appendTo(W);},null,this);}else{this.appendTo(W);}}else{if(!V&&R&&R!=O){Q=R.parentNode;if(Q){this.fireEvent("beforeAppendTo",{type:"beforeAppendTo",target:Q});Q.replaceChild(O,R);this.fireEvent("appendTo",{type:"appendTo",target:Q});}}else{if(this.get("type")!="link"&&V&&R&&R==O){this._addListenersToForm();}}}},initAttributes:function(O){var N=O||{};YAHOO.widget.Button.superclass.initAttributes.call(this,N);this.setAttributeConfig("type",{value:(N.type||"push"),validator:I.isString,writeOnce:true,method:this._setType});this.setAttributeConfig("label",{value:N.label,validator:I.isString,method:this._setLabel});this.setAttributeConfig("value",{value:N.value});this.setAttributeConfig("name",{value:N.name,validator:I.isString});this.setAttributeConfig("tabindex",{value:N.tabindex,validator:I.isNumber,method:this._setTabIndex});this.configureAttribute("title",{value:N.title,validator:I.isString,method:this._setTitle});this.setAttributeConfig("disabled",{value:(N.disabled||false),validator:I.isBoolean,method:this._setDisabled});this.setAttributeConfig("href",{value:N.href,validator:I.isString,method:this._setHref});this.setAttributeConfig("target",{value:N.target,validator:I.isString,method:this._setTarget});this.setAttributeConfig("checked",{value:(N.checked||false),validator:I.isBoolean,method:this._setChecked});this.setAttributeConfig("container",{value:N.container,writeOnce:true});this.setAttributeConfig("srcelement",{value:N.srcelement,writeOnce:true});this.setAttributeConfig("menu",{value:null,method:this._setMenu,writeOnce:true});this.setAttributeConfig("lazyloadmenu",{value:(N.lazyloadmenu===false?false:true),validator:I.isBoolean,writeOnce:true});this.setAttributeConfig("menuclassname",{value:(N.menuclassname||"yui-button-menu"),validator:I.isString,method:this._setMenuClassName,writeOnce:true});this.setAttributeConfig("selectedMenuItem",{value:null,method:this._setSelectedMenuItem});this.setAttributeConfig("onclick",{value:N.onclick,method:this._setOnClick});this.setAttributeConfig("focusmenu",{value:(N.focusmenu===false?false:true),validator:I.isBoolean});},focus:function(){if(!this.get("disabled")){this._button.focus();}},blur:function(){if(!this.get("disabled")){this._button.blur();}},hasFocus:function(){return(C==this);},isActive:function(){return this.hasClass(this.CSS_CLASS_NAME+"-active");},getMenu:function(){return this._menu;},getForm:function(){return this._button.form;},getHiddenFields:function(){return this._hiddenFields;},destroy:function(){var P=this.get("element"),O=P.parentNode,N=this._menu,R;if(N){if(K&&K.find(N)){K.remove(N);}N.destroy();}M.purgeElement(P);M.purgeElement(this._button);M.removeListener(document,"mouseup",this._onDocumentMouseUp);M.removeListener(document,"keyup",this._onDocumentKeyUp);M.removeListener(document,"mousedown",this._onDocumentMouseDown);var Q=this.getForm();if(Q){M.removeListener(Q,"reset",this._onFormReset);M.removeListener(Q,"submit",this.createHiddenFields);}this.unsubscribeAll();if(O){O.removeChild(P);}delete D[this.get("id")];R=G.getElementsByClassName(this.CSS_CLASS_NAME,this.NODE_NAME,Q);if(I.isArray(R)&&R.length===0){M.removeListener(Q,"keypress",YAHOO.widget.Button.onFormKeyPress);}},fireEvent:function(O,N){var P=arguments[0];if(this.DOM_EVENTS[P]&&this.get("disabled")){return ;}return YAHOO.widget.Button.superclass.fireEvent.apply(this,arguments);},toString:function(){return("Button "+this.get("id"));}});YAHOO.widget.Button.onFormKeyPress=function(R){var P=M.getTarget(R),S=M.getCharCode(R),Q=P.nodeName&&P.nodeName.toUpperCase(),N=P.type,T=false,V,W,O,X;function U(a){var Z,Y;switch(a.nodeName.toUpperCase()){case"INPUT":case"BUTTON":if(a.type=="submit"&&!a.disabled){if(!T&&!O){O=a;}if(W&&!X){X=a;}}break;default:Z=a.id;if(Z){V=D[Z];if(V){T=true;if(!V.get("disabled")){Y=V.get("srcelement");if(!W&&(V.get("type")=="submit"||(Y&&Y.type=="submit"))){W=V;}}}}break;}}if(S==13&&((Q=="INPUT"&&(N=="text"||N=="password"||N=="checkbox"||N=="radio"||N=="file"))||Q=="SELECT")){G.getElementsBy(U,"*",this);if(O){O.focus();}else{if(!O&&W){if(X){M.preventDefault(R);}W.submitForm();}}}};YAHOO.widget.Button.addHiddenFieldsToForm=function(N){var S=G.getElementsByClassName(YAHOO.widget.Button.prototype.CSS_CLASS_NAME,"*",N),Q=S.length,R,O,P;if(Q>0){for(P=0;P0){F=H-1;do{this._buttons[F].set("disabled",G);}while(F--);}},_onKeyDown:function(K){var G=B.getTarget(K),I=B.getCharCode(K),H=G.parentNode.parentNode.id,J=E[H],F=-1;if(I==37||I==38){F=(J.index===0)?(this._buttons.length-1):(J.index-1);
+}else{if(I==39||I==40){F=(J.index===(this._buttons.length-1))?0:(J.index+1);}}if(F>-1){this.check(F);this.getButton(F).focus();}},_onAppendTo:function(H){var I=this._buttons,G=I.length,F;for(F=0;F0){this.addButtons(J);}function F(K){return(K.type=="radio");}J=C.getElementsBy(F,"input",this.get("element"));if(J.length>0){this.addButtons(J);}this.on("keydown",this._onKeyDown);this.on("appendTo",this._onAppendTo);var G=this.get("container");if(G){if(D.isString(G)){B.onContentReady(G,function(){this.appendTo(G);},null,this);}else{this.appendTo(G);}}},initAttributes:function(G){var F=G||{};YAHOO.widget.ButtonGroup.superclass.initAttributes.call(this,F);this.setAttributeConfig("name",{value:F.name,validator:D.isString});this.setAttributeConfig("disabled",{value:(F.disabled||false),validator:D.isBoolean,method:this._setDisabled});this.setAttributeConfig("value",{value:F.value});this.setAttributeConfig("container",{value:F.container,writeOnce:true});this.setAttributeConfig("checkedButton",{value:null});},addButton:function(J){var L,K,G,F,H,I;if(J instanceof A&&J.get("type")=="radio"){L=J;}else{if(!D.isString(J)&&!J.nodeName){J.type="radio";L=new A(J);}else{L=new A(J,{type:"radio"});}}if(L){F=this._buttons.length;H=L.get("name");I=this.get("name");L.index=F;this._buttons[F]=L;E[L.get("id")]=L;if(H!=I){L.set("name",I);}if(this.get("disabled")){L.set("disabled",true);}if(L.get("checked")){this.set("checkedButton",L);}K=L.get("element");G=this.get("element");if(K.parentNode!=G){G.appendChild(K);}L.on("checkedChange",this._onButtonCheckedChange,L,this);return L;}},addButtons:function(G){var H,I,J,F;if(D.isArray(G)){H=G.length;J=[];if(H>0){for(F=0;F0){return J;}}}},removeButton:function(H){var I=this.getButton(H),G,F;if(I){this._buttons.splice(H,1);delete E[I.get("id")];I.removeListener("checkedChange",this._onButtonCheckedChange);I.destroy();G=this._buttons.length;if(G>0){F=this._buttons.length-1;do{this._buttons[F].index=F;}while(F--);}}},getButton:function(F){if(D.isNumber(F)){return this._buttons[F];}},getButtons:function(){return this._buttons;},getCount:function(){return this._buttons.length;},focus:function(H){var I,G,F;if(D.isNumber(H)){I=this._buttons[H];if(I){I.focus();}}else{G=this.getCount();for(F=0;F0){G=this._buttons.length-1;do{this._buttons[G].destroy();}while(G--);}B.purgeElement(H);F.removeChild(H);},toString:function(){return("ButtonGroup "+this.get("id"));}});})();YAHOO.register("button",YAHOO.widget.Button,{version:"2.5.2",build:"1076"});
\ No newline at end of file
diff --git a/lib/yui/button/button.js b/lib/yui/button/button.js
index ff3a1aa0b9..d3680712ce 100644
--- a/lib/yui/button/button.js
+++ b/lib/yui/button/button.js
@@ -2,7 +2,7 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
/**
* @module button
@@ -80,6 +80,7 @@ version: 2.5.0
var Dom = YAHOO.util.Dom,
Event = YAHOO.util.Event,
Lang = YAHOO.lang,
+ UA = YAHOO.env.ua,
Overlay = YAHOO.widget.Overlay,
Menu = YAHOO.widget.Menu,
@@ -120,7 +121,7 @@ version: 2.5.0
if (Lang.isString(p_sType) && Lang.isString(p_sName)) {
- if (YAHOO.env.ua.ie) {
+ if (UA.ie) {
/*
For IE it is necessary to create the element with the
@@ -809,6 +810,7 @@ version: 2.5.0
_setLabel: function (p_sLabel) {
this._button.innerHTML = p_sLabel;
+
/*
Remove and add the default class name from the root element
@@ -821,21 +823,17 @@ version: 2.5.0
*/
var sClass,
- me;
+ nGeckoVersion = UA.gecko;
+
- if (YAHOO.env.ua.gecko && Dom.inDocument(this.get("element"))) {
+ if (nGeckoVersion && nGeckoVersion < 1.9 && Dom.inDocument(this.get("element"))) {
- me = this;
sClass = this.CSS_CLASS_NAME;
this.removeClass(sClass);
- window.setTimeout(function () {
-
- me.addClass(sClass);
-
- }, 0);
-
+ Lang.later(0, this, this.addClass, sClass);
+
}
},
@@ -1410,9 +1408,32 @@ version: 2.5.0
* @return {Boolean}
*/
_isSplitButtonOptionKey: function (p_oEvent) {
+
+ var bShowMenu = (p_oEvent.ctrlKey && p_oEvent.shiftKey &&
+ Event.getCharCode(p_oEvent) == 77);
+
+
+ function onKeyPress(p_oEvent) {
+
+ Event.preventDefault(p_oEvent);
+
+ this.removeListener("keypress", onKeyPress);
+
+ }
+
+
+ /*
+ It is necessary to add a "keypress" event listener to prevent Opera's default
+ browser context menu from appearing when the user presses Ctrl + Shift + M.
+ */
+
+ if (bShowMenu && UA.opera) {
+
+ this.on("keypress", onKeyPress);
+
+ }
- return (p_oEvent.ctrlKey && p_oEvent.shiftKey &&
- Event.getCharCode(p_oEvent) == 77);
+ return bShowMenu;
},
@@ -1585,6 +1606,11 @@ version: 2.5.0
oMenu.align("bl", "tl");
}
+ else {
+
+ oMenu.align("tl", "bl");
+
+ }
}
@@ -1628,12 +1654,12 @@ version: 2.5.0
if (Menu && oMenu && (oMenu instanceof Menu)) {
- oMenu.cfg.applyConfig({ context: [oButtonEL, "tl", "bl"],
- clicktohide: false,
- visible: true });
+ oMenu.cfg.applyConfig({ context: [oButtonEL, "tl", "bl"], clicktohide: false });
oMenu.cfg.fireQueue();
+ oMenu.show();
+
oMenu.cfg.setProperty("maxheight", 0);
oMenu.align("tl", "bl");
@@ -2859,7 +2885,7 @@ version: 2.5.0
}
- if (YAHOO.env.ua.ie) {
+ if (UA.ie) {
bSubmitForm = oForm.fireEvent("onsubmit");
@@ -2881,7 +2907,7 @@ version: 2.5.0
method as well.
*/
- if ((YAHOO.env.ua.ie || YAHOO.env.ua.webkit) && bSubmitForm) {
+ if ((UA.ie || UA.webkit) && bSubmitForm) {
oForm.submit();
@@ -4667,4 +4693,4 @@ version: 2.5.0
});
})();
-YAHOO.register("button", YAHOO.widget.Button, {version: "2.5.0", build: "895"});
+YAHOO.register("button", YAHOO.widget.Button, {version: "2.5.2", build: "1076"});
diff --git a/lib/yui/calendar/README b/lib/yui/calendar/README
new file mode 100755
index 0000000000..1b27dbb056
--- /dev/null
+++ b/lib/yui/calendar/README
@@ -0,0 +1,402 @@
+Calendar Release Notes
+
+*** version 2.5.2 ***
+
++ CalendarGroup toDate method no longer throws javascript exception
+
+*** version 2.5.1 ***
+
++ Fixed bug with mindate, maxdate being applied incorrectly if
+ set to a day on which time change took place (DST, E.U Summertime)
+ and the day is not the first day of the week.
+
++ Fixed DateMath.getWeekNumber implementation to return correct
+ week numbers. The older implementation would return Week 0 for
+ certain weeks (e.g. the week starting Sun Dec 28th 2008)
+
+ To suppor the fix, DateMath.getWeekNumber has a signature
+ change in 2.5.1 and can now support U.S Week calculations based
+ on Jan 1st identifying the first week of the year, as well as
+ ISO8601 week calculations based on Jan 4th identifying the first
+ week of the year
+
+ The arguments which the method expected prior to 2.5.1 were not
+ being used in calculating the week number. The new signature is:
+
+ DateMath.getWeekNumber(Date dt, Number firstDayOfWeek, Number janDate)
+
+ Where:
+
+ dt is the date for which week number is required
+
+ firstDayOfWeek is the day index identifying the first
+ day of the week. Default is 0 (Sunday).
+
+ janDate is the date in the first week of January, which
+ identifies the first week of the year.
+ Default is YAHOO.widget.DateMath.WEEK_ONE_JAN_DATE (1)
+
+ NOTE: Calendar instances themselves do not currently expose a
+ configuration property to change the week numbering system
+ used. A "janDate" value is not passed to the getWeekNumber
+ method, when used by Calendar, resulting in it using the default value.
+
+ Therefore, ISO8601 week numbering can be generated for Calendars
+ by setting the value of YAHOO.widget.DateMath.WEEK_ONE_JAN_DATE
+ to 4.
+
+*** version 2.5.0 ***
+
++ Prevent default event handling in CalendarNavigator enter key
+ listener, to prevent automatic form submission when using Calendar
+ inside a form.
+
++ Added workaround to DateMath.add and subtract for Safari 2 (webkit)
+ bug in Date.setDate(n) which doesn't handle value of n less than -128
+ or greater than 127 correctly.
+
+ See: http://brianary.blogspot.com/2006/03/safari-date-bug.html
+
++ Added border, padding and margin rules to Calendar Sam Skin to
+ protect Sam Skin's look and feel when Calendar is used with
+ YUI base.css
+
+*** version 2.4.0 ***
+
++ Added CalendarNavigator (year selector) feature to allow the user to
+ jump to a year/month directly without having to scroll through months
+ sequentially.
+
+ The feature is enabled/configured using the "navigator" configuration
+ property.
+
++ Added Custom Events:
+
+ showNav/beforeShowNav
+ hideNav/beforeHideNav,
+ renderNav/beforeRenderNav
+
+ To Calendar/CalendarGroup, in support of the CalendarNavigator
+ functionality.
+
++ Added Custom Events:
+
+ show/beforeShow
+ hide/beforeHide
+
+ To Calendar and CalendarGroup. Returning false from a
+ beforeShow/beforeHide listener can be used to prevent the Calendar
+ from being shown/hidden respectively.
+
++ Added Public Methods:
+
+ getCellIndex(date) [ Calendar ]
+ getCalendarPage(date) [ CalendarGroup ]
+ toDate(dateArray) [ Calendar/CalendarGroup ]
+ removeRenderers() [ Calendar/CalendarGroup ]
+
++ The Calendar/CalendarGroup constructor is now more flexible:
+
+ * It no longer requires an "id" argument.
+
+ In it's simplest form, a Calendar/CalendarGroup can be
+ constructed by simply providing a container id or reference.
+
+ var cal = new YAHOO.widget.Calendar("container");
+ -or-
+ var containerDiv = YAHOO.util.Dom.get("container");
+ var cal = new YAHOO.widget.Calendar(containerDiv);
+
+ An id for the Calendar does not need to be provided, and will be
+ generated from the container id by appending an "_t" suffix to the
+ container id if only the container is provided.
+
+ * The container argument can be either a string, representing the
+ id of the container, or an HTMLElement referring to the container
+ element itself, as suggested in the example above.
+
+ * If an HTMLElement is provided for the container argument and the
+ element does not have an id, one will be generated for it using
+ YAHOO.util.Dom.generateId().
+
+ * The older form of Calendar/CalendarGroup signature, expecting
+ both an id and containerId is still supported and works as it did
+ prior to 2.4.0.
+
++ Fixed performance issue, where the same custom renderer was being
+ applied multiple times to the same cell.
+
++ Added getDate(year, month, date) factory method to the DateMath utility,
+ which can be used to create JavaScript Date instances for years less
+ than 100.
+
+ The default Date(year, month, date) constructor implementations across
+ browsers, assume that if year < 100, the caller is referring to the
+ nineteen hundreds, and the year is set to 19xx instead of xx (as with
+ the deprecated setYear method). However Date.setFullYear(xx) can
+ be used to set dates below 100. The above factory method provides a
+ construction mechanism consistent with setFullYear.
+
++ Changed Calendar/CalendarGroup/DateMath code to use the DateMath.getDate
+ method, so that 2 digit years are not assumed to be in the 1900's.
+
+ NOTE: Calendar's API already expects 4 digit date strings when referring
+ to years after 999.
+
+*** version 2.3.1 ***
+
++ Changed Calendar/CalendarGroup to render an empty title bar element
+ when "close" is set to true, but "title" has not been set, to allow Sam
+ Skin to render a title bar correctly.
+
+*** version 2.3.0 ***
+
++ Added checks to select, selectCell, deselect and deselectCell methods
+ to ensure the Calendar/Calendar group was not set to an invalid state
+ by programmatically selecting unselectable dates or cells.
+
++ Added new locale configuration properties for the Month/Year label
+ used in the Calendar header (MY_LABEL_MONTH_POSITION,
+ MY_LABEL_YEAR_POSITION, MY_LABEL_YEAR_SUFFIX, MY_LABEL_MONTH_SUFFIX).
+ Japan is an example locale, where customization of the Month/Year
+ label is required.
+
++ Changed "first", "last" class names to "first-of-type", "last-of-type",
+ to avoid collision with YUI Grids' use of the "first" class name.
+
++ Added public isDateOOB method, to check if a given date is outside of
+ the minimum/maximum configuration dates of the Calendar.
+
++ Deprecated YAHOO.widget.Calendar.browser, refactored to use
+ YAHOO.env.ua instead.
+
++ Removed overflow:hidden from default Calendar/CalendarGroup container
+ for non-IE6 browsers to fix clipping issue with IE7 when CalendarGroup
+ was inside a box with a specific width. overflow:hidden is still
+ required for IE6 with an iframe shim.
+
++ Added Opera container width calculation fix to CalendarGroup.show
+ method, to fix incorrect wrapping when using a CalendarGroup which is
+ initially rendered hidden (display:none). Previously this fix was
+ only applied on render.
+
+*** version 2.2.2 ***
+
++ Fixed problem with selected dates being shared across instances, when
+ more than one Calendar/CalendarGroup was on the page
+
+*** version 2.2.1 ***
+
++ Fixed problem with selectCell adding duplicate selected date entries
+ for dates which were already selected
+
++ Fixed problem with CalendarGroup iframe shim not covering the
+ CalendarGroup title area
+
++ Removed javascript:void(null) from close button and cell links which
+ was interrupting form submission and firing onbeforeunload in IE
+
++ Fixed problem with CalendarGroup getSelectedDates returning invalid
+ results, when used in conjunction with the "selected" Config property
+ (either passed in the constructor config argument or set seperately
+ after construction)
+
++ Refactored Calendar and CalendarGroup to improve performance,
+ especially when working with a large number of instances in
+ IE6
+
+*** version 2.2.0 ***
+
++ Image customization can now be done through CSS. Images for Close,
+ Left and Right Arrows are now pulled in using CSS defined in
+ calendar.css and by default use relative paths to the images in
+ the same directory as calendar.css.
+
++ Deprecated Calendar.IMG_ROOT and NAV_ARROW_LEFT, NAV_ARROW_RIGHT
+ configuration properties. Customizations based on older releases
+ which set these properties will still function as expected.
+
++ Deprecated CalendarGroup.CSS_2UPCLOSE. Calendar's Style.CSS_CLOSE
+ property now represents the new default CSS class (calclose) for
+ the close button. CSS_2UPCLOSE is still applied along with
+ CSS_CLOSE to the new markup for the close button to support existing
+ customizations of the CSS_2UPCLOSE CSS class (close-icon)
+
++ Fixed problem with Safari setting Calendar pages to incorrect dates
+ if the pages spanned a year boundary in CalendarGroups with 3 or more
+ pages, due to a bug in Safari's implementation of Date setMonth
+
++ Fixed problem with CalendarGroup setMonth rendering Calendar pages
+ with incorrect dates in all browsers if current pages spanned year
+ boundary
+
++ Fixed incorrect CalendarGroup logging statement in calendar-debug.js
+
++ Fixed domEventMap support for Safari versions prior to 2.0.2,
+ caused by hasOwnProperty not being supported
+
++ Removed unused private property : _pageDate from Calendar class
+
+*** version 0.12.2 ***
+
++ Corrected documentation for clearTime function to reflect the
+ change from midnight to noon
+
+*** version 0.12.1 ***
+
++ Calendar and CalendarGroup now automatically parse the argument
+ passed to setMonth and setYear into an integer, eliminating
+ potential concatenation bugs.
+
+*** version 0.12 ***
+
++ New documentation format implemented
+
++ Calendar2up and Calendar_Core are now deprecated. Now, Calendar alone
+ represents the single Calendar instance, and CalendarGroup represents
+ an n-up instance, defaulting to 2up
+
++ Added semantic style classes to Calendar elements to allow for
+ custom styling solely using CSS.
+
++ Remapped all configuration properties to use the Config object
+ (familiar to those who use the Container collection of controls).
+ Property names are the same as their previous counterparts, but
+ wrapped into Calendar.cfg, allowing for runtime reconfiguration of
+ most properties
+
++ Added "title" property for setting the Calendar title
+
++ Added "close" property for enabling and disabling the close icon
+
++ Added "iframe" property for enabling an iframe shim in Internet
+ Explorer 6 and below to fix the select bleed-through bug
+
++ pageDate moved to property: "pagedate"
+
++ selectedDates moved to property: "selected"
+
++ minDate moved to property : "mindate", which accepts a JavaScript
+ Date object like its predecessor, but also supports string dates
+
++ maxDate moved to property : "maxdate", which accepts a JavaScript
+ Date object like its predecessor, but also supports string dates
+
++ Moved style declarations to initStyles function
+
++ Optimized event handling in doSelectCell/doCellMouseOver/
+ doCellMouseOut by only attaching the listener to the outer
+ Calendar container, and only reacting to events on cells with
+ the "selectable" CSS class.
+
++ Added domEventMap field for applying DOM event listeners to cells
+ containing specific class and tag combinations.
+
++ Moved all cell DOM event attachment to applyListeners function
+
++ Added getDateByCellId / getDateFieldsByCellId helper functions
+
++ Corrected DateMath.getWeekNumber to comply with ISO week number
+ handling
+
++ Separated renderCellDefault style portions into styleCellDefault
+ function for easy extension
+
++ Deprecated onBeforeSelect. Created beforeSelectEvent which
+ automatically subscribes to its deprecated predecessor.
+
++ Deprecated onSelect. Created selectEvent, which automatically
+ subscribes to its deprecated predecessor.
+
++ Deprecated onBeforeDeselect. Created beforeSelectEvent which
+ automatically subscribes to its deprecated predecessor.
+
++ Deprecated onDeselect. Created beforeDeselectEvent, which
+ automatically subscribes to its deprecated predecessor.
+
++ Deprecated onChangePage. Created changePageEvent, which automatically
+ subscribes to its deprecated predecessor.
+
++ Deprecated onRender. Created renderEvent, which automatically
+ subscribes to its deprecated predecessor.
+
++ Deprecated onReset. Created resetEvent, which automatically
+ subscribes to its deprecated predecessor.
+
++ Deprecated onClear. Created clearEvent, which automatically
+ subscribes to its deprecated predecessor.
+
++ Corrected setMonth documentation to refer to 0-11 indexed months.
+
++ Added show and hide methods to Calendar for setting the Calendar's
+ display property.
+
++ Optimized internal render classes to use innerHTML and string buffers
+
++ Removed wireCustomEvents function
+
++ Removed wireDefaultEvents function
+
++ Removed doNextMonth / doPreviousMonth
+
++ Removed all buildShell (header, body, footer) functions, since
+ the Calendar shell is now built dynamically on each render
+
++ Wired all CalendarGroup events and configuration properties to
+ be properly delegated to Calendar
+
++ Augmented CalendarGroup with all built-in renderers, label functions,
+ hide, show, and initStyles, creating API transparency between Calendar
+ and CalendarGroup.
+
++ Made all tagName, createElement, and entity references XHTML compliant
+
++ Fixed Daylight Saving Time bug for Brazilian time zone
+
+*** version 0.11.3 ***
+
++ Calendar_Core: Added arguments for selected/deselected dates to
+ onSelect/onDeselect
+
++ CalendarGroup: Fixed bug where selected dates passed to constructor
+ were not represented in selectedDates
+
++ Calendar2up: Now displays correctly in Opera 9
+
+*** version 0.11.0 ***
+
++ DateMath: DateMath.add now properly adds weeks
+
++ DateMath: between() function added
+
++ DateMath: getWeekNumber() fixed to take starting day of week into
+ account
+
++ All references to Calendar's built in CSS class handlers are
+ removed, replaced with calls to Dom utility (addClass, removeClass)
+
++ Several CSS class constants now have clearer names
+
++ All CSS classes are now properly namespaced to avoid CSS conflicts
+
++ Fixed table:hover bug in CSS
+
++ Calendar no longer requires the container ID and variable name to
+ match in order for month navigation to function properly
+
++ Calendar month navigation arrows are now represented as
+ background images
+
+*** version 0.10.0 ***
+
++ Major performance improvements from attaching DOM events to
+ associated table cells only once, when the Calendar shell is built
+
++ DOM events for mouseover/mouseout are now fired for all browsers
+ (not just Internet Explorer)
+
++ Reset functionality bug fixed for 2-up Calendar view
+
+*** version 0.9.0 ***
+
+* Initial release
\ No newline at end of file
diff --git a/lib/yui/calendar/assets/calendar-core.css b/lib/yui/calendar/assets/calendar-core.css
index 294405181c..f1f3d1100a 100755
--- a/lib/yui/calendar/assets/calendar-core.css
+++ b/lib/yui/calendar/assets/calendar-core.css
@@ -2,7 +2,7 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
/**
* CORE
diff --git a/lib/yui/calendar/assets/calendar.css b/lib/yui/calendar/assets/calendar.css
index 26975d21dd..2d551ac2b1 100755
--- a/lib/yui/calendar/assets/calendar.css
+++ b/lib/yui/calendar/assets/calendar.css
@@ -2,7 +2,7 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
.yui-calcontainer {
position:relative;
diff --git a/lib/yui/calendar/assets/skins/sam/calendar-skin.css b/lib/yui/calendar/assets/skins/sam/calendar-skin.css
index 2cb9a73b36..c529957c30 100755
--- a/lib/yui/calendar/assets/skins/sam/calendar-skin.css
+++ b/lib/yui/calendar/assets/skins/sam/calendar-skin.css
@@ -2,7 +2,7 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
/**
* SAM
diff --git a/lib/yui/calendar/assets/skins/sam/calendar.css b/lib/yui/calendar/assets/skins/sam/calendar.css
index 53d4a7bd72..1742eeb84d 100755
--- a/lib/yui/calendar/assets/skins/sam/calendar.css
+++ b/lib/yui/calendar/assets/skins/sam/calendar.css
@@ -2,6 +2,6 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
.yui-calcontainer{position:relative;float:left;_overflow:hidden;}.yui-calcontainer iframe{position:absolute;border:none;margin:0;padding:0;z-index:0;width:100%;height:100%;left:0px;top:0px;}.yui-calcontainer iframe.fixedsize{width:50em;height:50em;top:-1px;left:-1px;}.yui-calcontainer.multi .groupcal{z-index:1;float:left;position:relative;}.yui-calcontainer .title{position:relative;z-index:1;}.yui-calcontainer .close-icon{position:absolute;z-index:1;}.yui-calendar{position:relative;}.yui-calendar .calnavleft{position:absolute;z-index:1;}.yui-calendar .calnavright{position:absolute;z-index:1;}.yui-calendar .calheader{position:relative;width:100%;text-align:center;}.yui-calcontainer .yui-cal-nav-mask{position:absolute;z-index:2;margin:0;padding:0;width:100%;height:100%;_width:0;_height:0;left:0;top:0;display:none;}.yui-calcontainer .yui-cal-nav{position:absolute;z-index:3;top:0;display:none;}.yui-calcontainer .yui-cal-nav .yui-cal-nav-btn{display:-moz-inline-box;display:inline-block;}.yui-calcontainer .yui-cal-nav .yui-cal-nav-btn button{display:block;*display:inline-block;*overflow:visible;border:none;background-color:transparent;cursor:pointer;}.yui-calendar .calbody a:hover{background:inherit;}p#clear{clear:left;padding-top:10px;}.yui-skin-sam .yui-calcontainer{background-color:#f2f2f2;border:1px solid #808080;padding:10px;}.yui-skin-sam .yui-calcontainer.multi{padding:0 5px 0 5px;}.yui-skin-sam .yui-calcontainer.multi .groupcal{background-color:transparent;border:none;padding:10px 5px 10px 5px;margin:0;}.yui-skin-sam .yui-calcontainer .title{background:url(../../../../assets/skins/sam/sprite.png) repeat-x 0 0;border-bottom:1px solid #cccccc;font:100% sans-serif;color:#000;font-weight:bold;height:auto;padding:.4em;margin:0 -10px 10px -10px;top:0;left:0;text-align:left;}.yui-skin-sam .yui-calcontainer.multi .title{margin:0 -5px 0 -5px;}.yui-skin-sam .yui-calcontainer.withtitle{padding-top:0;}.yui-skin-sam .yui-calcontainer .calclose{background:url(../../../../assets/skins/sam/sprite.png) no-repeat 0 -300px;width:25px;height:15px;top:.4em;right:.4em;cursor:pointer;}.yui-skin-sam .yui-calendar{border-spacing:0;border-collapse:collapse;font:100% sans-serif;text-align:center;margin:0;}.yui-skin-sam .yui-calendar .calhead{background:transparent;border:none;vertical-align:middle;padding:0;}.yui-skin-sam .yui-calendar .calheader{background:transparent;font-weight:bold;padding:0 0 .6em 0;text-align:center;}.yui-skin-sam .yui-calendar .calheader img{border:none;}.yui-skin-sam .yui-calendar .calnavleft{background:url(../../../../assets/skins/sam/sprite.png) no-repeat 0 -450px;width:25px;height:15px;top:0;bottom:0;left:-10px;margin-left:.4em;cursor:pointer;}.yui-skin-sam .yui-calendar .calnavright{background:url(../../../../assets/skins/sam/sprite.png) no-repeat 0 -500px;width:25px;height:15px;top:0;bottom:0;right:-10px;margin-right:.4em;cursor:pointer;}.yui-skin-sam .yui-calendar .calweekdayrow{height:2em;}.yui-skin-sam .yui-calendar .calweekdayrow th{padding:0;border:none;}.yui-skin-sam .yui-calendar .calweekdaycell{color:#000;font-weight:bold;text-align:center;width:2em;}.yui-skin-sam .yui-calendar .calfoot{background-color:#f2f2f2;}.yui-skin-sam .yui-calendar .calrowhead,.yui-skin-sam .yui-calendar .calrowfoot{color:#a6a6a6;font-size:85%;font-style:normal;font-weight:normal;border:none;}.yui-skin-sam .yui-calendar .calrowhead{text-align:right;padding:0 2px 0 0;}.yui-skin-sam .yui-calendar .calrowfoot{text-align:left;padding:0 0 0 2px;}.yui-skin-sam .yui-calendar td.calcell{border:1px solid #cccccc;background:#fff;padding:1px;height:1.6em;line-height:1.6em;text-align:center;white-space:nowrap;}.yui-skin-sam .yui-calendar td.calcell a{color:#0066cc;display:block;height:100%;text-decoration:none;}.yui-skin-sam .yui-calendar td.calcell.today{background-color:#000;}.yui-skin-sam .yui-calendar td.calcell.today a{background-color:#fff;}.yui-skin-sam .yui-calendar td.calcell.oom{background-color:#cccccc;color:#a6a6a6;cursor:default;}.yui-skin-sam .yui-calendar td.calcell.selected{background-color:#fff;color:#000;}.yui-skin-sam .yui-calendar td.calcell.selected a{background-color:#b3d4ff;color:#000;}.yui-skin-sam .yui-calendar td.calcell.calcellhover{background-color:#426fd9;color:#fff;cursor:pointer;}.yui-skin-sam .yui-calendar td.calcell.calcellhover a{background-color:#426fd9;color:#fff;}.yui-skin-sam .yui-calendar td.calcell.previous{color:#e0e0e0;}.yui-skin-sam .yui-calendar td.calcell.restricted{text-decoration:line-through;}.yui-skin-sam .yui-calendar td.calcell.highlight1{background-color:#ccff99;}.yui-skin-sam .yui-calendar td.calcell.highlight2{background-color:#99ccff;}.yui-skin-sam .yui-calendar td.calcell.highlight3{background-color:#ffcccc;}.yui-skin-sam .yui-calendar td.calcell.highlight4{background-color:#ccff99;}.yui-skin-sam .yui-calendar a.calnav{border:1px solid #f2f2f2;padding:0 4px;text-decoration:none;color:#000;zoom:1;}.yui-skin-sam .yui-calendar a.calnav:hover{background:url(../../../../assets/skins/sam/sprite.png) repeat-x 0 0;border-color:#A0A0A0;cursor:pointer;}.yui-skin-sam .yui-calcontainer .yui-cal-nav-mask{background-color:#000;opacity:0.25;*filter:alpha(opacity=25);}.yui-skin-sam .yui-calcontainer .yui-cal-nav{font-family:arial,helvetica,clean,sans-serif;font-size:93%;border:1px solid #808080;left:50%;margin-left:-7em;width:14em;padding:0;top:2.5em;background-color:#f2f2f2;}.yui-skin-sam .yui-calcontainer.withtitle .yui-cal-nav{top:4.5em;}.yui-skin-sam .yui-calcontainer.multi .yui-cal-nav{width:16em;margin-left:-8em;}.yui-skin-sam .yui-calcontainer .yui-cal-nav-y,.yui-skin-sam .yui-calcontainer .yui-cal-nav-m,.yui-skin-sam .yui-calcontainer .yui-cal-nav-b{padding:5px 10px 5px 10px;}.yui-skin-sam .yui-calcontainer .yui-cal-nav-b{text-align:center;}.yui-skin-sam .yui-calcontainer .yui-cal-nav-e{margin-top:5px;padding:5px;background-color:#EDF5FF;border-top:1px solid black;display:none;}.yui-skin-sam .yui-calcontainer .yui-cal-nav label{display:block;font-weight:bold;}.yui-skin-sam .yui-calcontainer .yui-cal-nav-mc{width:100%;_width:auto;}.yui-skin-sam .yui-calcontainer .yui-cal-nav-y input.yui-invalid{background-color:#FFEE69;border:1px solid #000;}.yui-skin-sam .yui-calcontainer .yui-cal-nav-yc{width:4em;}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn{border:1px solid #808080;background:url(../../../../assets/skins/sam/sprite.png) repeat-x 0 0;background-color:#ccc;margin:auto .15em;}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn button{padding:0 8px;font-size:93%;line-height:2;*line-height:1.7;min-height:2em;*min-height:auto;color:#000;}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn.yui-default{border:1px solid #304369;background-color:#426fd9;background:url(../../../../assets/skins/sam/sprite.png) repeat-x 0 -1400px;}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn.yui-default button{color:#fff;}
diff --git a/lib/yui/calendar/calendar-debug.js b/lib/yui/calendar/calendar-debug.js
new file mode 100755
index 0000000000..f7fb2b14ff
--- /dev/null
+++ b/lib/yui/calendar/calendar-debug.js
@@ -0,0 +1,6881 @@
+/*
+Copyright (c) 2008, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 2.5.2
+*/
+(function () {
+
+ /**
+ * Config is a utility used within an Object to allow the implementer to
+ * maintain a list of local configuration properties and listen for changes
+ * to those properties dynamically using CustomEvent. The initial values are
+ * also maintained so that the configuration can be reset at any given point
+ * to its initial state.
+ * @namespace YAHOO.util
+ * @class Config
+ * @constructor
+ * @param {Object} owner The owner Object to which this Config Object belongs
+ */
+ YAHOO.util.Config = function (owner) {
+
+ if (owner) {
+ this.init(owner);
+ }
+
+ if (!owner) { YAHOO.log("No owner specified for Config object", "error", "Config"); }
+
+ };
+
+
+ var Lang = YAHOO.lang,
+ CustomEvent = YAHOO.util.CustomEvent,
+ Config = YAHOO.util.Config;
+
+
+ /**
+ * Constant representing the CustomEvent type for the config changed event.
+ * @property YAHOO.util.Config.CONFIG_CHANGED_EVENT
+ * @private
+ * @static
+ * @final
+ */
+ Config.CONFIG_CHANGED_EVENT = "configChanged";
+
+ /**
+ * Constant representing the boolean type string
+ * @property YAHOO.util.Config.BOOLEAN_TYPE
+ * @private
+ * @static
+ * @final
+ */
+ Config.BOOLEAN_TYPE = "boolean";
+
+ Config.prototype = {
+
+ /**
+ * Object reference to the owner of this Config Object
+ * @property owner
+ * @type Object
+ */
+ owner: null,
+
+ /**
+ * Boolean flag that specifies whether a queue is currently
+ * being executed
+ * @property queueInProgress
+ * @type Boolean
+ */
+ queueInProgress: false,
+
+ /**
+ * Maintains the local collection of configuration property objects and
+ * their specified values
+ * @property config
+ * @private
+ * @type Object
+ */
+ config: null,
+
+ /**
+ * Maintains the local collection of configuration property objects as
+ * they were initially applied.
+ * This object is used when resetting a property.
+ * @property initialConfig
+ * @private
+ * @type Object
+ */
+ initialConfig: null,
+
+ /**
+ * Maintains the local, normalized CustomEvent queue
+ * @property eventQueue
+ * @private
+ * @type Object
+ */
+ eventQueue: null,
+
+ /**
+ * Custom Event, notifying subscribers when Config properties are set
+ * (setProperty is called without the silent flag
+ * @event configChangedEvent
+ */
+ configChangedEvent: null,
+
+ /**
+ * Initializes the configuration Object and all of its local members.
+ * @method init
+ * @param {Object} owner The owner Object to which this Config
+ * Object belongs
+ */
+ init: function (owner) {
+
+ this.owner = owner;
+
+ this.configChangedEvent =
+ this.createEvent(Config.CONFIG_CHANGED_EVENT);
+
+ this.configChangedEvent.signature = CustomEvent.LIST;
+ this.queueInProgress = false;
+ this.config = {};
+ this.initialConfig = {};
+ this.eventQueue = [];
+
+ },
+
+ /**
+ * Validates that the value passed in is a Boolean.
+ * @method checkBoolean
+ * @param {Object} val The value to validate
+ * @return {Boolean} true, if the value is valid
+ */
+ checkBoolean: function (val) {
+ return (typeof val == Config.BOOLEAN_TYPE);
+ },
+
+ /**
+ * Validates that the value passed in is a number.
+ * @method checkNumber
+ * @param {Object} val The value to validate
+ * @return {Boolean} true, if the value is valid
+ */
+ checkNumber: function (val) {
+ return (!isNaN(val));
+ },
+
+ /**
+ * Fires a configuration property event using the specified value.
+ * @method fireEvent
+ * @private
+ * @param {String} key The configuration property's name
+ * @param {value} Object The value of the correct type for the property
+ */
+ fireEvent: function ( key, value ) {
+ YAHOO.log("Firing Config event: " + key + "=" + value, "info", "Config");
+ var property = this.config[key];
+
+ if (property && property.event) {
+ property.event.fire(value);
+ }
+ },
+
+ /**
+ * Adds a property to the Config Object's private config hash.
+ * @method addProperty
+ * @param {String} key The configuration property's name
+ * @param {Object} propertyObject The Object containing all of this
+ * property's arguments
+ */
+ addProperty: function ( key, propertyObject ) {
+ key = key.toLowerCase();
+ YAHOO.log("Added property: " + key, "info", "Config");
+
+ this.config[key] = propertyObject;
+
+ propertyObject.event = this.createEvent(key, { scope: this.owner });
+ propertyObject.event.signature = CustomEvent.LIST;
+
+
+ propertyObject.key = key;
+
+ if (propertyObject.handler) {
+ propertyObject.event.subscribe(propertyObject.handler,
+ this.owner);
+ }
+
+ this.setProperty(key, propertyObject.value, true);
+
+ if (! propertyObject.suppressEvent) {
+ this.queueProperty(key, propertyObject.value);
+ }
+
+ },
+
+ /**
+ * Returns a key-value configuration map of the values currently set in
+ * the Config Object.
+ * @method getConfig
+ * @return {Object} The current config, represented in a key-value map
+ */
+ getConfig: function () {
+
+ var cfg = {},
+ prop,
+ property;
+
+ for (prop in this.config) {
+ property = this.config[prop];
+ if (property && property.event) {
+ cfg[prop] = property.value;
+ }
+ }
+
+ return cfg;
+ },
+
+ /**
+ * Returns the value of specified property.
+ * @method getProperty
+ * @param {String} key The name of the property
+ * @return {Object} The value of the specified property
+ */
+ getProperty: function (key) {
+ var property = this.config[key.toLowerCase()];
+ if (property && property.event) {
+ return property.value;
+ } else {
+ return undefined;
+ }
+ },
+
+ /**
+ * Resets the specified property's value to its initial value.
+ * @method resetProperty
+ * @param {String} key The name of the property
+ * @return {Boolean} True is the property was reset, false if not
+ */
+ resetProperty: function (key) {
+
+ key = key.toLowerCase();
+
+ var property = this.config[key];
+
+ if (property && property.event) {
+
+ if (this.initialConfig[key] &&
+ !Lang.isUndefined(this.initialConfig[key])) {
+
+ this.setProperty(key, this.initialConfig[key]);
+
+ return true;
+
+ }
+
+ } else {
+
+ return false;
+ }
+
+ },
+
+ /**
+ * Sets the value of a property. If the silent property is passed as
+ * true, the property's event will not be fired.
+ * @method setProperty
+ * @param {String} key The name of the property
+ * @param {String} value The value to set the property to
+ * @param {Boolean} silent Whether the value should be set silently,
+ * without firing the property event.
+ * @return {Boolean} True, if the set was successful, false if it failed.
+ */
+ setProperty: function (key, value, silent) {
+
+ var property;
+
+ key = key.toLowerCase();
+ YAHOO.log("setProperty: " + key + "=" + value, "info", "Config");
+
+ if (this.queueInProgress && ! silent) {
+ // Currently running through a queue...
+ this.queueProperty(key,value);
+ return true;
+
+ } else {
+ property = this.config[key];
+ if (property && property.event) {
+ if (property.validator && !property.validator(value)) {
+ return false;
+ } else {
+ property.value = value;
+ if (! silent) {
+ this.fireEvent(key, value);
+ this.configChangedEvent.fire([key, value]);
+ }
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+ },
+
+ /**
+ * Sets the value of a property and queues its event to execute. If the
+ * event is already scheduled to execute, it is
+ * moved from its current position to the end of the queue.
+ * @method queueProperty
+ * @param {String} key The name of the property
+ * @param {String} value The value to set the property to
+ * @return {Boolean} true, if the set was successful, false if
+ * it failed.
+ */
+ queueProperty: function (key, value) {
+
+ key = key.toLowerCase();
+ YAHOO.log("queueProperty: " + key + "=" + value, "info", "Config");
+
+ var property = this.config[key],
+ foundDuplicate = false,
+ iLen,
+ queueItem,
+ queueItemKey,
+ queueItemValue,
+ sLen,
+ supercedesCheck,
+ qLen,
+ queueItemCheck,
+ queueItemCheckKey,
+ queueItemCheckValue,
+ i,
+ s,
+ q;
+
+ if (property && property.event) {
+
+ if (!Lang.isUndefined(value) && property.validator &&
+ !property.validator(value)) { // validator
+ return false;
+ } else {
+
+ if (!Lang.isUndefined(value)) {
+ property.value = value;
+ } else {
+ value = property.value;
+ }
+
+ foundDuplicate = false;
+ iLen = this.eventQueue.length;
+
+ for (i = 0; i < iLen; i++) {
+ queueItem = this.eventQueue[i];
+
+ if (queueItem) {
+ queueItemKey = queueItem[0];
+ queueItemValue = queueItem[1];
+
+ if (queueItemKey == key) {
+
+ /*
+ found a dupe... push to end of queue, null
+ current item, and break
+ */
+
+ this.eventQueue[i] = null;
+
+ this.eventQueue.push(
+ [key, (!Lang.isUndefined(value) ?
+ value : queueItemValue)]);
+
+ foundDuplicate = true;
+ break;
+ }
+ }
+ }
+
+ // this is a refire, or a new property in the queue
+
+ if (! foundDuplicate && !Lang.isUndefined(value)) {
+ this.eventQueue.push([key, value]);
+ }
+ }
+
+ if (property.supercedes) {
+
+ sLen = property.supercedes.length;
+
+ for (s = 0; s < sLen; s++) {
+
+ supercedesCheck = property.supercedes[s];
+ qLen = this.eventQueue.length;
+
+ for (q = 0; q < qLen; q++) {
+ queueItemCheck = this.eventQueue[q];
+
+ if (queueItemCheck) {
+ queueItemCheckKey = queueItemCheck[0];
+ queueItemCheckValue = queueItemCheck[1];
+
+ if (queueItemCheckKey ==
+ supercedesCheck.toLowerCase() ) {
+
+ this.eventQueue.push([queueItemCheckKey,
+ queueItemCheckValue]);
+
+ this.eventQueue[q] = null;
+ break;
+
+ }
+ }
+ }
+ }
+ }
+
+ YAHOO.log("Config event queue: " + this.outputEventQueue(), "info", "Config");
+
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Fires the event for a property using the property's current value.
+ * @method refireEvent
+ * @param {String} key The name of the property
+ */
+ refireEvent: function (key) {
+
+ key = key.toLowerCase();
+
+ var property = this.config[key];
+
+ if (property && property.event &&
+
+ !Lang.isUndefined(property.value)) {
+
+ if (this.queueInProgress) {
+
+ this.queueProperty(key);
+
+ } else {
+
+ this.fireEvent(key, property.value);
+
+ }
+
+ }
+ },
+
+ /**
+ * Applies a key-value Object literal to the configuration, replacing
+ * any existing values, and queueing the property events.
+ * Although the values will be set, fireQueue() must be called for their
+ * associated events to execute.
+ * @method applyConfig
+ * @param {Object} userConfig The configuration Object literal
+ * @param {Boolean} init When set to true, the initialConfig will
+ * be set to the userConfig passed in, so that calling a reset will
+ * reset the properties to the passed values.
+ */
+ applyConfig: function (userConfig, init) {
+
+ var sKey,
+ oConfig;
+
+ if (init) {
+ oConfig = {};
+ for (sKey in userConfig) {
+ if (Lang.hasOwnProperty(userConfig, sKey)) {
+ oConfig[sKey.toLowerCase()] = userConfig[sKey];
+ }
+ }
+ this.initialConfig = oConfig;
+ }
+
+ for (sKey in userConfig) {
+ if (Lang.hasOwnProperty(userConfig, sKey)) {
+ this.queueProperty(sKey, userConfig[sKey]);
+ }
+ }
+ },
+
+ /**
+ * Refires the events for all configuration properties using their
+ * current values.
+ * @method refresh
+ */
+ refresh: function () {
+
+ var prop;
+
+ for (prop in this.config) {
+ this.refireEvent(prop);
+ }
+ },
+
+ /**
+ * Fires the normalized list of queued property change events
+ * @method fireQueue
+ */
+ fireQueue: function () {
+
+ var i,
+ queueItem,
+ key,
+ value,
+ property;
+
+ this.queueInProgress = true;
+ for (i = 0;i < this.eventQueue.length; i++) {
+ queueItem = this.eventQueue[i];
+ if (queueItem) {
+
+ key = queueItem[0];
+ value = queueItem[1];
+ property = this.config[key];
+
+ property.value = value;
+
+ this.fireEvent(key,value);
+ }
+ }
+
+ this.queueInProgress = false;
+ this.eventQueue = [];
+ },
+
+ /**
+ * Subscribes an external handler to the change event for any
+ * given property.
+ * @method subscribeToConfigEvent
+ * @param {String} key The property name
+ * @param {Function} handler The handler function to use subscribe to
+ * the property's event
+ * @param {Object} obj The Object to use for scoping the event handler
+ * (see CustomEvent documentation)
+ * @param {Boolean} override Optional. If true, will override "this"
+ * within the handler to map to the scope Object passed into the method.
+ * @return {Boolean} True, if the subscription was successful,
+ * otherwise false.
+ */
+ subscribeToConfigEvent: function (key, handler, obj, override) {
+
+ var property = this.config[key.toLowerCase()];
+
+ if (property && property.event) {
+ if (!Config.alreadySubscribed(property.event, handler, obj)) {
+ property.event.subscribe(handler, obj, override);
+ }
+ return true;
+ } else {
+ return false;
+ }
+
+ },
+
+ /**
+ * Unsubscribes an external handler from the change event for any
+ * given property.
+ * @method unsubscribeFromConfigEvent
+ * @param {String} key The property name
+ * @param {Function} handler The handler function to use subscribe to
+ * the property's event
+ * @param {Object} obj The Object to use for scoping the event
+ * handler (see CustomEvent documentation)
+ * @return {Boolean} True, if the unsubscription was successful,
+ * otherwise false.
+ */
+ unsubscribeFromConfigEvent: function (key, handler, obj) {
+ var property = this.config[key.toLowerCase()];
+ if (property && property.event) {
+ return property.event.unsubscribe(handler, obj);
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Returns a string representation of the Config object
+ * @method toString
+ * @return {String} The Config object in string format.
+ */
+ toString: function () {
+ var output = "Config";
+ if (this.owner) {
+ output += " [" + this.owner.toString() + "]";
+ }
+ return output;
+ },
+
+ /**
+ * Returns a string representation of the Config object's current
+ * CustomEvent queue
+ * @method outputEventQueue
+ * @return {String} The string list of CustomEvents currently queued
+ * for execution
+ */
+ outputEventQueue: function () {
+
+ var output = "",
+ queueItem,
+ q,
+ nQueue = this.eventQueue.length;
+
+ for (q = 0; q < nQueue; q++) {
+ queueItem = this.eventQueue[q];
+ if (queueItem) {
+ output += queueItem[0] + "=" + queueItem[1] + ", ";
+ }
+ }
+ return output;
+ },
+
+ /**
+ * Sets all properties to null, unsubscribes all listeners from each
+ * property's change event and all listeners from the configChangedEvent.
+ * @method destroy
+ */
+ destroy: function () {
+
+ var oConfig = this.config,
+ sProperty,
+ oProperty;
+
+
+ for (sProperty in oConfig) {
+
+ if (Lang.hasOwnProperty(oConfig, sProperty)) {
+
+ oProperty = oConfig[sProperty];
+
+ oProperty.event.unsubscribeAll();
+ oProperty.event = null;
+
+ }
+
+ }
+
+ this.configChangedEvent.unsubscribeAll();
+
+ this.configChangedEvent = null;
+ this.owner = null;
+ this.config = null;
+ this.initialConfig = null;
+ this.eventQueue = null;
+
+ }
+
+ };
+
+
+
+ /**
+ * Checks to determine if a particular function/Object pair are already
+ * subscribed to the specified CustomEvent
+ * @method YAHOO.util.Config.alreadySubscribed
+ * @static
+ * @param {YAHOO.util.CustomEvent} evt The CustomEvent for which to check
+ * the subscriptions
+ * @param {Function} fn The function to look for in the subscribers list
+ * @param {Object} obj The execution scope Object for the subscription
+ * @return {Boolean} true, if the function/Object pair is already subscribed
+ * to the CustomEvent passed in
+ */
+ Config.alreadySubscribed = function (evt, fn, obj) {
+
+ var nSubscribers = evt.subscribers.length,
+ subsc,
+ i;
+
+ if (nSubscribers > 0) {
+ i = nSubscribers - 1;
+ do {
+ subsc = evt.subscribers[i];
+ if (subsc && subsc.obj == obj && subsc.fn == fn) {
+ return true;
+ }
+ }
+ while (i--);
+ }
+
+ return false;
+
+ };
+
+ YAHOO.lang.augmentProto(Config, YAHOO.util.EventProvider);
+
+}());
+
+/**
+* YAHOO.widget.DateMath is used for simple date manipulation. The class is a static utility
+* used for adding, subtracting, and comparing dates.
+* @namespace YAHOO.widget
+* @class DateMath
+*/
+YAHOO.widget.DateMath = {
+ /**
+ * Constant field representing Day
+ * @property DAY
+ * @static
+ * @final
+ * @type String
+ */
+ DAY : "D",
+
+ /**
+ * Constant field representing Week
+ * @property WEEK
+ * @static
+ * @final
+ * @type String
+ */
+ WEEK : "W",
+
+ /**
+ * Constant field representing Year
+ * @property YEAR
+ * @static
+ * @final
+ * @type String
+ */
+ YEAR : "Y",
+
+ /**
+ * Constant field representing Month
+ * @property MONTH
+ * @static
+ * @final
+ * @type String
+ */
+ MONTH : "M",
+
+ /**
+ * Constant field representing one day, in milliseconds
+ * @property ONE_DAY_MS
+ * @static
+ * @final
+ * @type Number
+ */
+ ONE_DAY_MS : 1000*60*60*24,
+
+ /**
+ * Constant field representing the date in first week of January
+ * which identifies the first week of the year.
+ *
+ * In the U.S, Jan 1st is normally used based on a Sunday start of week.
+ * ISO 8601, used widely throughout Europe, uses Jan 4th, based on a Monday start of week.
+ *
+ * @property WEEK_ONE_JAN_DATE
+ * @static
+ * @type Number
+ */
+ WEEK_ONE_JAN_DATE : 1,
+
+ /**
+ * Adds the specified amount of time to the this instance.
+ * @method add
+ * @param {Date} date The JavaScript Date object to perform addition on
+ * @param {String} field The field constant to be used for performing addition.
+ * @param {Number} amount The number of units (measured in the field constant) to add to the date.
+ * @return {Date} The resulting Date object
+ */
+ add : function(date, field, amount) {
+ var d = new Date(date.getTime());
+ switch (field) {
+ case this.MONTH:
+ var newMonth = date.getMonth() + amount;
+ var years = 0;
+
+ if (newMonth < 0) {
+ while (newMonth < 0) {
+ newMonth += 12;
+ years -= 1;
+ }
+ } else if (newMonth > 11) {
+ while (newMonth > 11) {
+ newMonth -= 12;
+ years += 1;
+ }
+ }
+
+ d.setMonth(newMonth);
+ d.setFullYear(date.getFullYear() + years);
+ break;
+ case this.DAY:
+ this._addDays(d, amount);
+ // d.setDate(date.getDate() + amount);
+ break;
+ case this.YEAR:
+ d.setFullYear(date.getFullYear() + amount);
+ break;
+ case this.WEEK:
+ this._addDays(d, (amount * 7));
+ // d.setDate(date.getDate() + (amount * 7));
+ break;
+ }
+ return d;
+ },
+
+ /**
+ * Private helper method to account for bug in Safari 2 (webkit < 420)
+ * when Date.setDate(n) is called with n less than -128 or greater than 127.
+ *
+ * Fix approach and original findings are available here:
+ * http://brianary.blogspot.com/2006/03/safari-date-bug.html
+ *
+ * @method _addDays
+ * @param {Date} d JavaScript date object
+ * @param {Number} nDays The number of days to add to the date object (can be negative)
+ * @private
+ */
+ _addDays : function(d, nDays) {
+ if (YAHOO.env.ua.webkit && YAHOO.env.ua.webkit < 420) {
+ if (nDays < 0) {
+ // Ensure we don't go below -128 (getDate() is always 1 to 31, so we won't go above 127)
+ for(var min = -128; nDays < min; nDays -= min) {
+ d.setDate(d.getDate() + min);
+ }
+ } else {
+ // Ensure we don't go above 96 + 31 = 127
+ for(var max = 96; nDays > max; nDays -= max) {
+ d.setDate(d.getDate() + max);
+ }
+ }
+ // nDays should be remainder between -128 and 96
+ }
+ d.setDate(d.getDate() + nDays);
+ },
+
+ /**
+ * Subtracts the specified amount of time from the this instance.
+ * @method subtract
+ * @param {Date} date The JavaScript Date object to perform subtraction on
+ * @param {Number} field The this field constant to be used for performing subtraction.
+ * @param {Number} amount The number of units (measured in the field constant) to subtract from the date.
+ * @return {Date} The resulting Date object
+ */
+ subtract : function(date, field, amount) {
+ return this.add(date, field, (amount*-1));
+ },
+
+ /**
+ * Determines whether a given date is before another date on the calendar.
+ * @method before
+ * @param {Date} date The Date object to compare with the compare argument
+ * @param {Date} compareTo The Date object to use for the comparison
+ * @return {Boolean} true if the date occurs before the compared date; false if not.
+ */
+ before : function(date, compareTo) {
+ var ms = compareTo.getTime();
+ if (date.getTime() < ms) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Determines whether a given date is after another date on the calendar.
+ * @method after
+ * @param {Date} date The Date object to compare with the compare argument
+ * @param {Date} compareTo The Date object to use for the comparison
+ * @return {Boolean} true if the date occurs after the compared date; false if not.
+ */
+ after : function(date, compareTo) {
+ var ms = compareTo.getTime();
+ if (date.getTime() > ms) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Determines whether a given date is between two other dates on the calendar.
+ * @method between
+ * @param {Date} date The date to check for
+ * @param {Date} dateBegin The start of the range
+ * @param {Date} dateEnd The end of the range
+ * @return {Boolean} true if the date occurs between the compared dates; false if not.
+ */
+ between : function(date, dateBegin, dateEnd) {
+ if (this.after(date, dateBegin) && this.before(date, dateEnd)) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Retrieves a JavaScript Date object representing January 1 of any given year.
+ * @method getJan1
+ * @param {Number} calendarYear The calendar year for which to retrieve January 1
+ * @return {Date} January 1 of the calendar year specified.
+ */
+ getJan1 : function(calendarYear) {
+ return this.getDate(calendarYear,0,1);
+ },
+
+ /**
+ * Calculates the number of days the specified date is from January 1 of the specified calendar year.
+ * Passing January 1 to this function would return an offset value of zero.
+ * @method getDayOffset
+ * @param {Date} date The JavaScript date for which to find the offset
+ * @param {Number} calendarYear The calendar year to use for determining the offset
+ * @return {Number} The number of days since January 1 of the given year
+ */
+ getDayOffset : function(date, calendarYear) {
+ var beginYear = this.getJan1(calendarYear); // Find the start of the year. This will be in week 1.
+
+ // Find the number of days the passed in date is away from the calendar year start
+ var dayOffset = Math.ceil((date.getTime()-beginYear.getTime()) / this.ONE_DAY_MS);
+ return dayOffset;
+ },
+
+ /**
+ * Calculates the week number for the given date. Can currently support standard
+ * U.S. week numbers, based on Jan 1st defining the 1st week of the year, and
+ * ISO8601 week numbers, based on Jan 4th defining the 1st week of the year.
+ *
+ * @method getWeekNumber
+ * @param {Date} date The JavaScript date for which to find the week number
+ * @param {Number} firstDayOfWeek The index of the first day of the week (0 = Sun, 1 = Mon ... 6 = Sat).
+ * Defaults to 0
+ * @param {Number} janDate The date in the first week of January which defines week one for the year
+ * Defaults to the value of YAHOO.widget.DateMath.WEEK_ONE_JAN_DATE, which is 1 (Jan 1st).
+ * For the U.S, this is normally Jan 1st. ISO8601 uses Jan 4th to define the first week of the year.
+ *
+ * @return {Number} The number of the week containing the given date.
+ */
+ getWeekNumber : function(date, firstDayOfWeek, janDate) {
+
+ // Setup Defaults
+ firstDayOfWeek = firstDayOfWeek || 0;
+ janDate = janDate || this.WEEK_ONE_JAN_DATE;
+
+ var targetDate = this.clearTime(date),
+ startOfWeek,
+ endOfWeek;
+
+ if (targetDate.getDay() === firstDayOfWeek) {
+ startOfWeek = targetDate;
+ } else {
+ startOfWeek = this.getFirstDayOfWeek(targetDate, firstDayOfWeek);
+ }
+
+ var startYear = startOfWeek.getFullYear(),
+ startTime = startOfWeek.getTime();
+
+ // DST shouldn't be a problem here, math is quicker than setDate();
+ endOfWeek = new Date(startOfWeek.getTime() + 6*this.ONE_DAY_MS);
+
+ var weekNum;
+ if (startYear !== endOfWeek.getFullYear() && endOfWeek.getDate() >= janDate) {
+ // If years don't match, endOfWeek is in Jan. and if the
+ // week has WEEK_ONE_JAN_DATE in it, it's week one by definition.
+ weekNum = 1;
+ } else {
+ // Get the 1st day of the 1st week, and
+ // find how many days away we are from it.
+ var weekOne = this.clearTime(this.getDate(startYear, 0, janDate)),
+ weekOneDayOne = this.getFirstDayOfWeek(weekOne, firstDayOfWeek);
+
+ // Round days to smoothen out 1 hr DST diff
+ var daysDiff = Math.round((targetDate.getTime() - weekOneDayOne.getTime())/this.ONE_DAY_MS);
+
+ // Calc. Full Weeks
+ var rem = daysDiff % 7;
+ var weeksDiff = (daysDiff - rem)/7;
+ weekNum = weeksDiff + 1;
+ }
+ return weekNum;
+ },
+
+ /**
+ * Get the first day of the week, for the give date.
+ * @param {Date} dt The date in the week for which the first day is required.
+ * @param {Number} startOfWeek The index for the first day of the week, 0 = Sun, 1 = Mon ... 6 = Sat (defaults to 0)
+ * @return {Date} The first day of the week
+ */
+ getFirstDayOfWeek : function (dt, startOfWeek) {
+ startOfWeek = startOfWeek || 0;
+ var dayOfWeekIndex = dt.getDay(),
+ dayOfWeek = (dayOfWeekIndex - startOfWeek + 7) % 7;
+
+ return this.subtract(dt, this.DAY, dayOfWeek);
+ },
+
+ /**
+ * Determines if a given week overlaps two different years.
+ * @method isYearOverlapWeek
+ * @param {Date} weekBeginDate The JavaScript Date representing the first day of the week.
+ * @return {Boolean} true if the date overlaps two different years.
+ */
+ isYearOverlapWeek : function(weekBeginDate) {
+ var overlaps = false;
+ var nextWeek = this.add(weekBeginDate, this.DAY, 6);
+ if (nextWeek.getFullYear() != weekBeginDate.getFullYear()) {
+ overlaps = true;
+ }
+ return overlaps;
+ },
+
+ /**
+ * Determines if a given week overlaps two different months.
+ * @method isMonthOverlapWeek
+ * @param {Date} weekBeginDate The JavaScript Date representing the first day of the week.
+ * @return {Boolean} true if the date overlaps two different months.
+ */
+ isMonthOverlapWeek : function(weekBeginDate) {
+ var overlaps = false;
+ var nextWeek = this.add(weekBeginDate, this.DAY, 6);
+ if (nextWeek.getMonth() != weekBeginDate.getMonth()) {
+ overlaps = true;
+ }
+ return overlaps;
+ },
+
+ /**
+ * Gets the first day of a month containing a given date.
+ * @method findMonthStart
+ * @param {Date} date The JavaScript Date used to calculate the month start
+ * @return {Date} The JavaScript Date representing the first day of the month
+ */
+ findMonthStart : function(date) {
+ var start = this.getDate(date.getFullYear(), date.getMonth(), 1);
+ return start;
+ },
+
+ /**
+ * Gets the last day of a month containing a given date.
+ * @method findMonthEnd
+ * @param {Date} date The JavaScript Date used to calculate the month end
+ * @return {Date} The JavaScript Date representing the last day of the month
+ */
+ findMonthEnd : function(date) {
+ var start = this.findMonthStart(date);
+ var nextMonth = this.add(start, this.MONTH, 1);
+ var end = this.subtract(nextMonth, this.DAY, 1);
+ return end;
+ },
+
+ /**
+ * Clears the time fields from a given date, effectively setting the time to 12 noon.
+ * @method clearTime
+ * @param {Date} date The JavaScript Date for which the time fields will be cleared
+ * @return {Date} The JavaScript Date cleared of all time fields
+ */
+ clearTime : function(date) {
+ date.setHours(12,0,0,0);
+ return date;
+ },
+
+ /**
+ * Returns a new JavaScript Date object, representing the given year, month and date. Time fields (hr, min, sec, ms) on the new Date object
+ * are set to 0. The method allows Date instances to be created with the a year less than 100. "new Date(year, month, date)" implementations
+ * set the year to 19xx if a year (xx) which is less than 100 is provided.
+ *
+ * NOTE:Validation on argument values is not performed. It is the caller's responsibility to ensure
+ * arguments are valid as per the ECMAScript-262 Date object specification for the new Date(year, month[, date]) constructor.
+ *
+ * @method getDate
+ * @param {Number} y Year.
+ * @param {Number} m Month index from 0 (Jan) to 11 (Dec).
+ * @param {Number} d (optional) Date from 1 to 31. If not provided, defaults to 1.
+ * @return {Date} The JavaScript date object with year, month, date set as provided.
+ */
+ getDate : function(y, m, d) {
+ var dt = null;
+ if (YAHOO.lang.isUndefined(d)) {
+ d = 1;
+ }
+ if (y >= 100) {
+ dt = new Date(y, m, d);
+ } else {
+ dt = new Date();
+ dt.setFullYear(y);
+ dt.setMonth(m);
+ dt.setDate(d);
+ dt.setHours(0,0,0,0);
+ }
+ return dt;
+ }
+};
+
+/**
+* The Calendar component is a UI control that enables users to choose one or more dates from a graphical calendar presented in a one-month or
+* multi-month interface. Calendars are generated entirely via script and can be navigated without any page refreshes.
+* @module calendar
+* @title Calendar
+* @namespace YAHOO.widget
+* @requires yahoo,dom,event
+*/
+
+/**
+* Calendar is the base class for the Calendar widget. In its most basic
+* implementation, it has the ability to render a calendar widget on the page
+* that can be manipulated to select a single date, move back and forth between
+* months and years.
+*
To construct the placeholder for the calendar widget, the code is as
+* follows:
+*
+*
+*
+*
+*
+* NOTE: As of 2.4.0, the constructor's ID argument is optional.
+* The Calendar can be constructed by simply providing a container ID string,
+* or a reference to a container DIV HTMLElement (the element needs to exist
+* in the document).
+*
+* E.g.:
+*
+* var c = new YAHOO.widget.Calendar("calContainer", configOptions);
+*
+* or:
+*
+* var containerDiv = YAHOO.util.Dom.get("calContainer");
+* var c = new YAHOO.widget.Calendar(containerDiv, configOptions);
+*
+*
+*
+* If not provided, the ID will be generated from the container DIV ID by adding an "_t" suffix.
+* For example if an ID is not provided, and the container's ID is "calContainer", the Calendar's ID will be set to "calContainer_t".
+*
+*
+* @namespace YAHOO.widget
+* @class Calendar
+* @constructor
+* @param {String} id optional The id of the table element that will represent the Calendar widget. As of 2.4.0, this argument is optional.
+* @param {String | HTMLElement} container The id of the container div element that will wrap the Calendar table, or a reference to a DIV element which exists in the document.
+* @param {Object} config optional The configuration object containing the initial configuration values for the Calendar.
+*/
+YAHOO.widget.Calendar = function(id, containerId, config) {
+ this.init.apply(this, arguments);
+};
+
+/**
+* The path to be used for images loaded for the Calendar
+* @property YAHOO.widget.Calendar.IMG_ROOT
+* @static
+* @deprecated You can now customize images by overriding the calclose, calnavleft and calnavright default CSS classes for the close icon, left arrow and right arrow respectively
+* @type String
+*/
+YAHOO.widget.Calendar.IMG_ROOT = null;
+
+/**
+* Type constant used for renderers to represent an individual date (M/D/Y)
+* @property YAHOO.widget.Calendar.DATE
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.DATE = "D";
+
+/**
+* Type constant used for renderers to represent an individual date across any year (M/D)
+* @property YAHOO.widget.Calendar.MONTH_DAY
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.MONTH_DAY = "MD";
+
+/**
+* Type constant used for renderers to represent a weekday
+* @property YAHOO.widget.Calendar.WEEKDAY
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.WEEKDAY = "WD";
+
+/**
+* Type constant used for renderers to represent a range of individual dates (M/D/Y-M/D/Y)
+* @property YAHOO.widget.Calendar.RANGE
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.RANGE = "R";
+
+/**
+* Type constant used for renderers to represent a month across any year
+* @property YAHOO.widget.Calendar.MONTH
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.MONTH = "M";
+
+/**
+* Constant that represents the total number of date cells that are displayed in a given month
+* @property YAHOO.widget.Calendar.DISPLAY_DAYS
+* @static
+* @final
+* @type Number
+*/
+YAHOO.widget.Calendar.DISPLAY_DAYS = 42;
+
+/**
+* Constant used for halting the execution of the remainder of the render stack
+* @property YAHOO.widget.Calendar.STOP_RENDER
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.STOP_RENDER = "S";
+
+/**
+* Constant used to represent short date field string formats (e.g. Tu or Feb)
+* @property YAHOO.widget.Calendar.SHORT
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.SHORT = "short";
+
+/**
+* Constant used to represent long date field string formats (e.g. Monday or February)
+* @property YAHOO.widget.Calendar.LONG
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.LONG = "long";
+
+/**
+* Constant used to represent medium date field string formats (e.g. Mon)
+* @property YAHOO.widget.Calendar.MEDIUM
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.MEDIUM = "medium";
+
+/**
+* Constant used to represent single character date field string formats (e.g. M, T, W)
+* @property YAHOO.widget.Calendar.ONE_CHAR
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.ONE_CHAR = "1char";
+
+/**
+* The set of default Config property keys and values for the Calendar
+* @property YAHOO.widget.Calendar._DEFAULT_CONFIG
+* @final
+* @static
+* @private
+* @type Object
+*/
+YAHOO.widget.Calendar._DEFAULT_CONFIG = {
+ // Default values for pagedate and selected are not class level constants - they are set during instance creation
+ PAGEDATE : {key:"pagedate", value:null},
+ SELECTED : {key:"selected", value:null},
+ TITLE : {key:"title", value:""},
+ CLOSE : {key:"close", value:false},
+ IFRAME : {key:"iframe", value:(YAHOO.env.ua.ie && YAHOO.env.ua.ie <= 6) ? true : false},
+ MINDATE : {key:"mindate", value:null},
+ MAXDATE : {key:"maxdate", value:null},
+ MULTI_SELECT : {key:"multi_select", value:false},
+ START_WEEKDAY : {key:"start_weekday", value:0},
+ SHOW_WEEKDAYS : {key:"show_weekdays", value:true},
+ SHOW_WEEK_HEADER : {key:"show_week_header", value:false},
+ SHOW_WEEK_FOOTER : {key:"show_week_footer", value:false},
+ HIDE_BLANK_WEEKS : {key:"hide_blank_weeks", value:false},
+ NAV_ARROW_LEFT: {key:"nav_arrow_left", value:null} ,
+ NAV_ARROW_RIGHT : {key:"nav_arrow_right", value:null} ,
+ MONTHS_SHORT : {key:"months_short", value:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]},
+ MONTHS_LONG: {key:"months_long", value:["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]},
+ WEEKDAYS_1CHAR: {key:"weekdays_1char", value:["S", "M", "T", "W", "T", "F", "S"]},
+ WEEKDAYS_SHORT: {key:"weekdays_short", value:["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]},
+ WEEKDAYS_MEDIUM: {key:"weekdays_medium", value:["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]},
+ WEEKDAYS_LONG: {key:"weekdays_long", value:["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]},
+ LOCALE_MONTHS:{key:"locale_months", value:"long"},
+ LOCALE_WEEKDAYS:{key:"locale_weekdays", value:"short"},
+ DATE_DELIMITER:{key:"date_delimiter", value:","},
+ DATE_FIELD_DELIMITER:{key:"date_field_delimiter", value:"/"},
+ DATE_RANGE_DELIMITER:{key:"date_range_delimiter", value:"-"},
+ MY_MONTH_POSITION:{key:"my_month_position", value:1},
+ MY_YEAR_POSITION:{key:"my_year_position", value:2},
+ MD_MONTH_POSITION:{key:"md_month_position", value:1},
+ MD_DAY_POSITION:{key:"md_day_position", value:2},
+ MDY_MONTH_POSITION:{key:"mdy_month_position", value:1},
+ MDY_DAY_POSITION:{key:"mdy_day_position", value:2},
+ MDY_YEAR_POSITION:{key:"mdy_year_position", value:3},
+ MY_LABEL_MONTH_POSITION:{key:"my_label_month_position", value:1},
+ MY_LABEL_YEAR_POSITION:{key:"my_label_year_position", value:2},
+ MY_LABEL_MONTH_SUFFIX:{key:"my_label_month_suffix", value:" "},
+ MY_LABEL_YEAR_SUFFIX:{key:"my_label_year_suffix", value:""},
+ NAV: {key:"navigator", value: null}
+};
+
+/**
+* The set of Custom Event types supported by the Calendar
+* @property YAHOO.widget.Calendar._EVENT_TYPES
+* @final
+* @static
+* @private
+* @type Object
+*/
+YAHOO.widget.Calendar._EVENT_TYPES = {
+ BEFORE_SELECT : "beforeSelect",
+ SELECT : "select",
+ BEFORE_DESELECT : "beforeDeselect",
+ DESELECT : "deselect",
+ CHANGE_PAGE : "changePage",
+ BEFORE_RENDER : "beforeRender",
+ RENDER : "render",
+ RESET : "reset",
+ CLEAR : "clear",
+ BEFORE_HIDE : "beforeHide",
+ HIDE : "hide",
+ BEFORE_SHOW : "beforeShow",
+ SHOW : "show",
+ BEFORE_HIDE_NAV : "beforeHideNav",
+ HIDE_NAV : "hideNav",
+ BEFORE_SHOW_NAV : "beforeShowNav",
+ SHOW_NAV : "showNav",
+ BEFORE_RENDER_NAV : "beforeRenderNav",
+ RENDER_NAV : "renderNav"
+};
+
+/**
+* The set of default style constants for the Calendar
+* @property YAHOO.widget.Calendar._STYLES
+* @final
+* @static
+* @private
+* @type Object
+*/
+YAHOO.widget.Calendar._STYLES = {
+ CSS_ROW_HEADER: "calrowhead",
+ CSS_ROW_FOOTER: "calrowfoot",
+ CSS_CELL : "calcell",
+ CSS_CELL_SELECTOR : "selector",
+ CSS_CELL_SELECTED : "selected",
+ CSS_CELL_SELECTABLE : "selectable",
+ CSS_CELL_RESTRICTED : "restricted",
+ CSS_CELL_TODAY : "today",
+ CSS_CELL_OOM : "oom",
+ CSS_CELL_OOB : "previous",
+ CSS_HEADER : "calheader",
+ CSS_HEADER_TEXT : "calhead",
+ CSS_BODY : "calbody",
+ CSS_WEEKDAY_CELL : "calweekdaycell",
+ CSS_WEEKDAY_ROW : "calweekdayrow",
+ CSS_FOOTER : "calfoot",
+ CSS_CALENDAR : "yui-calendar",
+ CSS_SINGLE : "single",
+ CSS_CONTAINER : "yui-calcontainer",
+ CSS_NAV_LEFT : "calnavleft",
+ CSS_NAV_RIGHT : "calnavright",
+ CSS_NAV : "calnav",
+ CSS_CLOSE : "calclose",
+ CSS_CELL_TOP : "calcelltop",
+ CSS_CELL_LEFT : "calcellleft",
+ CSS_CELL_RIGHT : "calcellright",
+ CSS_CELL_BOTTOM : "calcellbottom",
+ CSS_CELL_HOVER : "calcellhover",
+ CSS_CELL_HIGHLIGHT1 : "highlight1",
+ CSS_CELL_HIGHLIGHT2 : "highlight2",
+ CSS_CELL_HIGHLIGHT3 : "highlight3",
+ CSS_CELL_HIGHLIGHT4 : "highlight4"
+};
+
+YAHOO.widget.Calendar.prototype = {
+
+ /**
+ * The configuration object used to set up the calendars various locale and style options.
+ * @property Config
+ * @private
+ * @deprecated Configuration properties should be set by calling Calendar.cfg.setProperty.
+ * @type Object
+ */
+ Config : null,
+
+ /**
+ * The parent CalendarGroup, only to be set explicitly by the parent group
+ * @property parent
+ * @type CalendarGroup
+ */
+ parent : null,
+
+ /**
+ * The index of this item in the parent group
+ * @property index
+ * @type Number
+ */
+ index : -1,
+
+ /**
+ * The collection of calendar table cells
+ * @property cells
+ * @type HTMLTableCellElement[]
+ */
+ cells : null,
+
+ /**
+ * The collection of calendar cell dates that is parallel to the cells collection. The array contains dates field arrays in the format of [YYYY, M, D].
+ * @property cellDates
+ * @type Array[](Number[])
+ */
+ cellDates : null,
+
+ /**
+ * The id that uniquely identifies this Calendar.
+ * @property id
+ * @type String
+ */
+ id : null,
+
+ /**
+ * The unique id associated with the Calendar's container
+ * @property containerId
+ * @type String
+ */
+ containerId: null,
+
+ /**
+ * The DOM element reference that points to this calendar's container element. The calendar will be inserted into this element when the shell is rendered.
+ * @property oDomContainer
+ * @type HTMLElement
+ */
+ oDomContainer : null,
+
+ /**
+ * A Date object representing today's date.
+ * @property today
+ * @type Date
+ */
+ today : null,
+
+ /**
+ * The list of render functions, along with required parameters, used to render cells.
+ * @property renderStack
+ * @type Array[]
+ */
+ renderStack : null,
+
+ /**
+ * A copy of the initial render functions created before rendering.
+ * @property _renderStack
+ * @private
+ * @type Array
+ */
+ _renderStack : null,
+
+ /**
+ * A reference to the CalendarNavigator instance created for this Calendar.
+ * Will be null if the "navigator" configuration property has not been set
+ * @property oNavigator
+ * @type CalendarNavigator
+ */
+ oNavigator : null,
+
+ /**
+ * The private list of initially selected dates.
+ * @property _selectedDates
+ * @private
+ * @type Array
+ */
+ _selectedDates : null,
+
+ /**
+ * A map of DOM event handlers to attach to cells associated with specific CSS class names
+ * @property domEventMap
+ * @type Object
+ */
+ domEventMap : null,
+
+ /**
+ * Protected helper used to parse Calendar constructor/init arguments.
+ *
+ * As of 2.4.0, Calendar supports a simpler constructor
+ * signature. This method reconciles arguments
+ * received in the pre 2.4.0 and 2.4.0 formats.
+ *
+ * @protected
+ * @method _parseArgs
+ * @param {Array} Function "arguments" array
+ * @return {Object} Object with id, container, config properties containing
+ * the reconciled argument values.
+ **/
+ _parseArgs : function(args) {
+ /*
+ 2.4.0 Constructors signatures
+
+ new Calendar(String)
+ new Calendar(HTMLElement)
+ new Calendar(String, ConfigObject)
+ new Calendar(HTMLElement, ConfigObject)
+
+ Pre 2.4.0 Constructor signatures
+
+ new Calendar(String, String)
+ new Calendar(String, HTMLElement)
+ new Calendar(String, String, ConfigObject)
+ new Calendar(String, HTMLElement, ConfigObject)
+ */
+ var nArgs = {id:null, container:null, config:null};
+
+ if (args && args.length && args.length > 0) {
+ switch (args.length) {
+ case 1:
+ nArgs.id = null;
+ nArgs.container = args[0];
+ nArgs.config = null;
+ break;
+ case 2:
+ if (YAHOO.lang.isObject(args[1]) && !args[1].tagName && !(args[1] instanceof String)) {
+ nArgs.id = null;
+ nArgs.container = args[0];
+ nArgs.config = args[1];
+ } else {
+ nArgs.id = args[0];
+ nArgs.container = args[1];
+ nArgs.config = null;
+ }
+ break;
+ default: // 3+
+ nArgs.id = args[0];
+ nArgs.container = args[1];
+ nArgs.config = args[2];
+ break;
+ }
+ } else {
+ this.logger.log("Invalid constructor/init arguments", "error");
+ }
+ return nArgs;
+ },
+
+ /**
+ * Initializes the Calendar widget.
+ * @method init
+ *
+ * @param {String} id optional The id of the table element that will represent the Calendar widget. As of 2.4.0, this argument is optional.
+ * @param {String | HTMLElement} container The id of the container div element that will wrap the Calendar table, or a reference to a DIV element which exists in the document.
+ * @param {Object} config optional The configuration object containing the initial configuration values for the Calendar.
+ */
+ init : function(id, container, config) {
+ // Normalize 2.4.0, pre 2.4.0 args
+ var nArgs = this._parseArgs(arguments);
+
+ id = nArgs.id;
+ container = nArgs.container;
+ config = nArgs.config;
+
+ this.oDomContainer = YAHOO.util.Dom.get(container);
+ if (!this.oDomContainer) { this.logger.log("Container not found in document.", "error"); }
+
+ if (!this.oDomContainer.id) {
+ this.oDomContainer.id = YAHOO.util.Dom.generateId();
+ }
+ if (!id) {
+ id = this.oDomContainer.id + "_t";
+ }
+
+ this.id = id;
+ this.containerId = this.oDomContainer.id;
+
+ this.logger = new YAHOO.widget.LogWriter("Calendar " + this.id);
+ this.initEvents();
+
+ this.today = new Date();
+ YAHOO.widget.DateMath.clearTime(this.today);
+
+ /**
+ * The Config object used to hold the configuration variables for the Calendar
+ * @property cfg
+ * @type YAHOO.util.Config
+ */
+ this.cfg = new YAHOO.util.Config(this);
+
+ /**
+ * The local object which contains the Calendar's options
+ * @property Options
+ * @type Object
+ */
+ this.Options = {};
+
+ /**
+ * The local object which contains the Calendar's locale settings
+ * @property Locale
+ * @type Object
+ */
+ this.Locale = {};
+
+ this.initStyles();
+
+ YAHOO.util.Dom.addClass(this.oDomContainer, this.Style.CSS_CONTAINER);
+ YAHOO.util.Dom.addClass(this.oDomContainer, this.Style.CSS_SINGLE);
+
+ this.cellDates = [];
+ this.cells = [];
+ this.renderStack = [];
+ this._renderStack = [];
+
+ this.setupConfig();
+
+ if (config) {
+ this.cfg.applyConfig(config, true);
+ }
+
+ this.cfg.fireQueue();
+ },
+
+ /**
+ * Default Config listener for the iframe property. If the iframe config property is set to true,
+ * renders the built-in IFRAME shim if the container is relatively or absolutely positioned.
+ *
+ * @method configIframe
+ */
+ configIframe : function(type, args, obj) {
+ var useIframe = args[0];
+
+ if (!this.parent) {
+ if (YAHOO.util.Dom.inDocument(this.oDomContainer)) {
+ if (useIframe) {
+ var pos = YAHOO.util.Dom.getStyle(this.oDomContainer, "position");
+
+ if (pos == "absolute" || pos == "relative") {
+
+ if (!YAHOO.util.Dom.inDocument(this.iframe)) {
+ this.iframe = document.createElement("iframe");
+ this.iframe.src = "javascript:false;";
+
+ YAHOO.util.Dom.setStyle(this.iframe, "opacity", "0");
+
+ if (YAHOO.env.ua.ie && YAHOO.env.ua.ie <= 6) {
+ YAHOO.util.Dom.addClass(this.iframe, "fixedsize");
+ }
+
+ this.oDomContainer.insertBefore(this.iframe, this.oDomContainer.firstChild);
+ }
+ }
+ } else {
+ if (this.iframe) {
+ if (this.iframe.parentNode) {
+ this.iframe.parentNode.removeChild(this.iframe);
+ }
+ this.iframe = null;
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Default handler for the "title" property
+ * @method configTitle
+ */
+ configTitle : function(type, args, obj) {
+ var title = args[0];
+
+ // "" disables title bar
+ if (title) {
+ this.createTitleBar(title);
+ } else {
+ var close = this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.CLOSE.key);
+ if (!close) {
+ this.removeTitleBar();
+ } else {
+ this.createTitleBar(" ");
+ }
+ }
+ },
+
+ /**
+ * Default handler for the "close" property
+ * @method configClose
+ */
+ configClose : function(type, args, obj) {
+ var close = args[0],
+ title = this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.TITLE.key);
+
+ if (close) {
+ if (!title) {
+ this.createTitleBar(" ");
+ }
+ this.createCloseButton();
+ } else {
+ this.removeCloseButton();
+ if (!title) {
+ this.removeTitleBar();
+ }
+ }
+ },
+
+ /**
+ * Initializes Calendar's built-in CustomEvents
+ * @method initEvents
+ */
+ initEvents : function() {
+
+ var defEvents = YAHOO.widget.Calendar._EVENT_TYPES;
+
+ /**
+ * Fired before a selection is made
+ * @event beforeSelectEvent
+ */
+ this.beforeSelectEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_SELECT);
+
+ /**
+ * Fired when a selection is made
+ * @event selectEvent
+ * @param {Array} Array of Date field arrays in the format [YYYY, MM, DD].
+ */
+ this.selectEvent = new YAHOO.util.CustomEvent(defEvents.SELECT);
+
+ /**
+ * Fired before a selection is made
+ * @event beforeDeselectEvent
+ */
+ this.beforeDeselectEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_DESELECT);
+
+ /**
+ * Fired when a selection is made
+ * @event deselectEvent
+ * @param {Array} Array of Date field arrays in the format [YYYY, MM, DD].
+ */
+ this.deselectEvent = new YAHOO.util.CustomEvent(defEvents.DESELECT);
+
+ /**
+ * Fired when the Calendar page is changed
+ * @event changePageEvent
+ */
+ this.changePageEvent = new YAHOO.util.CustomEvent(defEvents.CHANGE_PAGE);
+
+ /**
+ * Fired before the Calendar is rendered
+ * @event beforeRenderEvent
+ */
+ this.beforeRenderEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_RENDER);
+
+ /**
+ * Fired when the Calendar is rendered
+ * @event renderEvent
+ */
+ this.renderEvent = new YAHOO.util.CustomEvent(defEvents.RENDER);
+
+ /**
+ * Fired when the Calendar is reset
+ * @event resetEvent
+ */
+ this.resetEvent = new YAHOO.util.CustomEvent(defEvents.RESET);
+
+ /**
+ * Fired when the Calendar is cleared
+ * @event clearEvent
+ */
+ this.clearEvent = new YAHOO.util.CustomEvent(defEvents.CLEAR);
+
+ /**
+ * Fired just before the Calendar is to be shown
+ * @event beforeShowEvent
+ */
+ this.beforeShowEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_SHOW);
+
+ /**
+ * Fired after the Calendar is shown
+ * @event showEvent
+ */
+ this.showEvent = new YAHOO.util.CustomEvent(defEvents.SHOW);
+
+ /**
+ * Fired just before the Calendar is to be hidden
+ * @event beforeHideEvent
+ */
+ this.beforeHideEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_HIDE);
+
+ /**
+ * Fired after the Calendar is hidden
+ * @event hideEvent
+ */
+ this.hideEvent = new YAHOO.util.CustomEvent(defEvents.HIDE);
+
+ /**
+ * Fired just before the CalendarNavigator is to be shown
+ * @event beforeShowNavEvent
+ */
+ this.beforeShowNavEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_SHOW_NAV);
+
+ /**
+ * Fired after the CalendarNavigator is shown
+ * @event showNavEvent
+ */
+ this.showNavEvent = new YAHOO.util.CustomEvent(defEvents.SHOW_NAV);
+
+ /**
+ * Fired just before the CalendarNavigator is to be hidden
+ * @event beforeHideNavEvent
+ */
+ this.beforeHideNavEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_HIDE_NAV);
+
+ /**
+ * Fired after the CalendarNavigator is hidden
+ * @event hideNavEvent
+ */
+ this.hideNavEvent = new YAHOO.util.CustomEvent(defEvents.HIDE_NAV);
+
+ /**
+ * Fired just before the CalendarNavigator is to be rendered
+ * @event beforeRenderNavEvent
+ */
+ this.beforeRenderNavEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_RENDER_NAV);
+
+ /**
+ * Fired after the CalendarNavigator is rendered
+ * @event renderNavEvent
+ */
+ this.renderNavEvent = new YAHOO.util.CustomEvent(defEvents.RENDER_NAV);
+
+ this.beforeSelectEvent.subscribe(this.onBeforeSelect, this, true);
+ this.selectEvent.subscribe(this.onSelect, this, true);
+ this.beforeDeselectEvent.subscribe(this.onBeforeDeselect, this, true);
+ this.deselectEvent.subscribe(this.onDeselect, this, true);
+ this.changePageEvent.subscribe(this.onChangePage, this, true);
+ this.renderEvent.subscribe(this.onRender, this, true);
+ this.resetEvent.subscribe(this.onReset, this, true);
+ this.clearEvent.subscribe(this.onClear, this, true);
+ },
+
+ /**
+ * The default event function that is attached to a date link within a calendar cell
+ * when the calendar is rendered.
+ * @method doSelectCell
+ * @param {DOMEvent} e The event
+ * @param {Calendar} cal A reference to the calendar passed by the Event utility
+ */
+ doSelectCell : function(e, cal) {
+ var cell,index,d,date;
+
+ var target = YAHOO.util.Event.getTarget(e);
+ var tagName = target.tagName.toLowerCase();
+ var defSelector = false;
+
+ while (tagName != "td" && ! YAHOO.util.Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
+
+ if (!defSelector && tagName == "a" && YAHOO.util.Dom.hasClass(target, cal.Style.CSS_CELL_SELECTOR)) {
+ defSelector = true;
+ }
+
+ target = target.parentNode;
+ tagName = target.tagName.toLowerCase();
+ // TODO: No need to go all the way up to html.
+ if (tagName == "html") {
+ return;
+ }
+ }
+
+ if (defSelector) {
+ // Stop link href navigation for default renderer
+ YAHOO.util.Event.preventDefault(e);
+ }
+
+ cell = target;
+
+ if (YAHOO.util.Dom.hasClass(cell, cal.Style.CSS_CELL_SELECTABLE)) {
+ index = cell.id.split("cell")[1];
+ d = cal.cellDates[index];
+ date = YAHOO.widget.DateMath.getDate(d[0],d[1]-1,d[2]);
+
+ var link;
+
+ cal.logger.log("Selecting cell " + index + " via click", "info");
+ if (cal.Options.MULTI_SELECT) {
+ link = cell.getElementsByTagName("a")[0];
+ if (link) {
+ link.blur();
+ }
+
+ var cellDate = cal.cellDates[index];
+ var cellDateIndex = cal._indexOfSelectedFieldArray(cellDate);
+
+ if (cellDateIndex > -1) {
+ cal.deselectCell(index);
+ } else {
+ cal.selectCell(index);
+ }
+
+ } else {
+ link = cell.getElementsByTagName("a")[0];
+ if (link) {
+ link.blur();
+ }
+ cal.selectCell(index);
+ }
+ }
+ },
+
+ /**
+ * The event that is executed when the user hovers over a cell
+ * @method doCellMouseOver
+ * @param {DOMEvent} e The event
+ * @param {Calendar} cal A reference to the calendar passed by the Event utility
+ */
+ doCellMouseOver : function(e, cal) {
+ var target;
+ if (e) {
+ target = YAHOO.util.Event.getTarget(e);
+ } else {
+ target = this;
+ }
+
+ while (target.tagName && target.tagName.toLowerCase() != "td") {
+ target = target.parentNode;
+ if (!target.tagName || target.tagName.toLowerCase() == "html") {
+ return;
+ }
+ }
+
+ if (YAHOO.util.Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
+ YAHOO.util.Dom.addClass(target, cal.Style.CSS_CELL_HOVER);
+ }
+ },
+
+ /**
+ * The event that is executed when the user moves the mouse out of a cell
+ * @method doCellMouseOut
+ * @param {DOMEvent} e The event
+ * @param {Calendar} cal A reference to the calendar passed by the Event utility
+ */
+ doCellMouseOut : function(e, cal) {
+ var target;
+ if (e) {
+ target = YAHOO.util.Event.getTarget(e);
+ } else {
+ target = this;
+ }
+
+ while (target.tagName && target.tagName.toLowerCase() != "td") {
+ target = target.parentNode;
+ if (!target.tagName || target.tagName.toLowerCase() == "html") {
+ return;
+ }
+ }
+
+ if (YAHOO.util.Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
+ YAHOO.util.Dom.removeClass(target, cal.Style.CSS_CELL_HOVER);
+ }
+ },
+
+ setupConfig : function() {
+
+ var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG;
+
+ /**
+ * The month/year representing the current visible Calendar date (mm/yyyy)
+ * @config pagedate
+ * @type String
+ * @default today's date
+ */
+ this.cfg.addProperty(defCfg.PAGEDATE.key, { value:new Date(), handler:this.configPageDate } );
+
+ /**
+ * The date or range of dates representing the current Calendar selection
+ * @config selected
+ * @type String
+ * @default []
+ */
+ this.cfg.addProperty(defCfg.SELECTED.key, { value:[], handler:this.configSelected } );
+
+ /**
+ * The title to display above the Calendar's month header
+ * @config title
+ * @type String
+ * @default ""
+ */
+ this.cfg.addProperty(defCfg.TITLE.key, { value:defCfg.TITLE.value, handler:this.configTitle } );
+
+ /**
+ * Whether or not a close button should be displayed for this Calendar
+ * @config close
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.CLOSE.key, { value:defCfg.CLOSE.value, handler:this.configClose } );
+
+ /**
+ * Whether or not an iframe shim should be placed under the Calendar to prevent select boxes from bleeding through in Internet Explorer 6 and below.
+ * This property is enabled by default for IE6 and below. It is disabled by default for other browsers for performance reasons, but can be
+ * enabled if required.
+ *
+ * @config iframe
+ * @type Boolean
+ * @default true for IE6 and below, false for all other browsers
+ */
+ this.cfg.addProperty(defCfg.IFRAME.key, { value:defCfg.IFRAME.value, handler:this.configIframe, validator:this.cfg.checkBoolean } );
+
+ /**
+ * The minimum selectable date in the current Calendar (mm/dd/yyyy)
+ * @config mindate
+ * @type String
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.MINDATE.key, { value:defCfg.MINDATE.value, handler:this.configMinDate } );
+
+ /**
+ * The maximum selectable date in the current Calendar (mm/dd/yyyy)
+ * @config maxdate
+ * @type String
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.MAXDATE.key, { value:defCfg.MAXDATE.value, handler:this.configMaxDate } );
+
+
+ // Options properties
+
+ /**
+ * True if the Calendar should allow multiple selections. False by default.
+ * @config MULTI_SELECT
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.MULTI_SELECT.key, { value:defCfg.MULTI_SELECT.value, handler:this.configOptions, validator:this.cfg.checkBoolean } );
+
+ /**
+ * The weekday the week begins on. Default is 0 (Sunday = 0, Monday = 1 ... Saturday = 6).
+ * @config START_WEEKDAY
+ * @type number
+ * @default 0
+ */
+ this.cfg.addProperty(defCfg.START_WEEKDAY.key, { value:defCfg.START_WEEKDAY.value, handler:this.configOptions, validator:this.cfg.checkNumber } );
+
+ /**
+ * True if the Calendar should show weekday labels. True by default.
+ * @config SHOW_WEEKDAYS
+ * @type Boolean
+ * @default true
+ */
+ this.cfg.addProperty(defCfg.SHOW_WEEKDAYS.key, { value:defCfg.SHOW_WEEKDAYS.value, handler:this.configOptions, validator:this.cfg.checkBoolean } );
+
+ /**
+ * True if the Calendar should show week row headers. False by default.
+ * @config SHOW_WEEK_HEADER
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.SHOW_WEEK_HEADER.key, { value:defCfg.SHOW_WEEK_HEADER.value, handler:this.configOptions, validator:this.cfg.checkBoolean } );
+
+ /**
+ * True if the Calendar should show week row footers. False by default.
+ * @config SHOW_WEEK_FOOTER
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.SHOW_WEEK_FOOTER.key,{ value:defCfg.SHOW_WEEK_FOOTER.value, handler:this.configOptions, validator:this.cfg.checkBoolean } );
+
+ /**
+ * True if the Calendar should suppress weeks that are not a part of the current month. False by default.
+ * @config HIDE_BLANK_WEEKS
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.HIDE_BLANK_WEEKS.key, { value:defCfg.HIDE_BLANK_WEEKS.value, handler:this.configOptions, validator:this.cfg.checkBoolean } );
+
+ /**
+ * The image that should be used for the left navigation arrow.
+ * @config NAV_ARROW_LEFT
+ * @type String
+ * @deprecated You can customize the image by overriding the default CSS class for the left arrow - "calnavleft"
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.NAV_ARROW_LEFT.key, { value:defCfg.NAV_ARROW_LEFT.value, handler:this.configOptions } );
+
+ /**
+ * The image that should be used for the right navigation arrow.
+ * @config NAV_ARROW_RIGHT
+ * @type String
+ * @deprecated You can customize the image by overriding the default CSS class for the right arrow - "calnavright"
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.NAV_ARROW_RIGHT.key, { value:defCfg.NAV_ARROW_RIGHT.value, handler:this.configOptions } );
+
+ // Locale properties
+
+ /**
+ * The short month labels for the current locale.
+ * @config MONTHS_SHORT
+ * @type String[]
+ * @default ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+ */
+ this.cfg.addProperty(defCfg.MONTHS_SHORT.key, { value:defCfg.MONTHS_SHORT.value, handler:this.configLocale } );
+
+ /**
+ * The long month labels for the current locale.
+ * @config MONTHS_LONG
+ * @type String[]
+ * @default ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
+ */
+ this.cfg.addProperty(defCfg.MONTHS_LONG.key, { value:defCfg.MONTHS_LONG.value, handler:this.configLocale } );
+
+ /**
+ * The 1-character weekday labels for the current locale.
+ * @config WEEKDAYS_1CHAR
+ * @type String[]
+ * @default ["S", "M", "T", "W", "T", "F", "S"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_1CHAR.key, { value:defCfg.WEEKDAYS_1CHAR.value, handler:this.configLocale } );
+
+ /**
+ * The short weekday labels for the current locale.
+ * @config WEEKDAYS_SHORT
+ * @type String[]
+ * @default ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_SHORT.key, { value:defCfg.WEEKDAYS_SHORT.value, handler:this.configLocale } );
+
+ /**
+ * The medium weekday labels for the current locale.
+ * @config WEEKDAYS_MEDIUM
+ * @type String[]
+ * @default ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_MEDIUM.key, { value:defCfg.WEEKDAYS_MEDIUM.value, handler:this.configLocale } );
+
+ /**
+ * The long weekday labels for the current locale.
+ * @config WEEKDAYS_LONG
+ * @type String[]
+ * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_LONG.key, { value:defCfg.WEEKDAYS_LONG.value, handler:this.configLocale } );
+
+ /**
+ * Refreshes the locale values used to build the Calendar.
+ * @method refreshLocale
+ * @private
+ */
+ var refreshLocale = function() {
+ this.cfg.refireEvent(defCfg.LOCALE_MONTHS.key);
+ this.cfg.refireEvent(defCfg.LOCALE_WEEKDAYS.key);
+ };
+
+ this.cfg.subscribeToConfigEvent(defCfg.START_WEEKDAY.key, refreshLocale, this, true);
+ this.cfg.subscribeToConfigEvent(defCfg.MONTHS_SHORT.key, refreshLocale, this, true);
+ this.cfg.subscribeToConfigEvent(defCfg.MONTHS_LONG.key, refreshLocale, this, true);
+ this.cfg.subscribeToConfigEvent(defCfg.WEEKDAYS_1CHAR.key, refreshLocale, this, true);
+ this.cfg.subscribeToConfigEvent(defCfg.WEEKDAYS_SHORT.key, refreshLocale, this, true);
+ this.cfg.subscribeToConfigEvent(defCfg.WEEKDAYS_MEDIUM.key, refreshLocale, this, true);
+ this.cfg.subscribeToConfigEvent(defCfg.WEEKDAYS_LONG.key, refreshLocale, this, true);
+
+ /**
+ * The setting that determines which length of month labels should be used. Possible values are "short" and "long".
+ * @config LOCALE_MONTHS
+ * @type String
+ * @default "long"
+ */
+ this.cfg.addProperty(defCfg.LOCALE_MONTHS.key, { value:defCfg.LOCALE_MONTHS.value, handler:this.configLocaleValues } );
+
+ /**
+ * The setting that determines which length of weekday labels should be used. Possible values are "1char", "short", "medium", and "long".
+ * @config LOCALE_WEEKDAYS
+ * @type String
+ * @default "short"
+ */
+ this.cfg.addProperty(defCfg.LOCALE_WEEKDAYS.key, { value:defCfg.LOCALE_WEEKDAYS.value, handler:this.configLocaleValues } );
+
+ /**
+ * The value used to delimit individual dates in a date string passed to various Calendar functions.
+ * @config DATE_DELIMITER
+ * @type String
+ * @default ","
+ */
+ this.cfg.addProperty(defCfg.DATE_DELIMITER.key, { value:defCfg.DATE_DELIMITER.value, handler:this.configLocale } );
+
+ /**
+ * The value used to delimit date fields in a date string passed to various Calendar functions.
+ * @config DATE_FIELD_DELIMITER
+ * @type String
+ * @default "/"
+ */
+ this.cfg.addProperty(defCfg.DATE_FIELD_DELIMITER.key, { value:defCfg.DATE_FIELD_DELIMITER.value, handler:this.configLocale } );
+
+ /**
+ * The value used to delimit date ranges in a date string passed to various Calendar functions.
+ * @config DATE_RANGE_DELIMITER
+ * @type String
+ * @default "-"
+ */
+ this.cfg.addProperty(defCfg.DATE_RANGE_DELIMITER.key, { value:defCfg.DATE_RANGE_DELIMITER.value, handler:this.configLocale } );
+
+ /**
+ * The position of the month in a month/year date string
+ * @config MY_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MY_MONTH_POSITION.key, { value:defCfg.MY_MONTH_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the year in a month/year date string
+ * @config MY_YEAR_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MY_YEAR_POSITION.key, { value:defCfg.MY_YEAR_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the month in a month/day date string
+ * @config MD_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MD_MONTH_POSITION.key, { value:defCfg.MD_MONTH_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the day in a month/year date string
+ * @config MD_DAY_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MD_DAY_POSITION.key, { value:defCfg.MD_DAY_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the month in a month/day/year date string
+ * @config MDY_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MDY_MONTH_POSITION.key, { value:defCfg.MDY_MONTH_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the day in a month/day/year date string
+ * @config MDY_DAY_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MDY_DAY_POSITION.key, { value:defCfg.MDY_DAY_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the year in a month/day/year date string
+ * @config MDY_YEAR_POSITION
+ * @type Number
+ * @default 3
+ */
+ this.cfg.addProperty(defCfg.MDY_YEAR_POSITION.key, { value:defCfg.MDY_YEAR_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the month in the month year label string used as the Calendar header
+ * @config MY_LABEL_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_MONTH_POSITION.key, { value:defCfg.MY_LABEL_MONTH_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the year in the month year label string used as the Calendar header
+ * @config MY_LABEL_YEAR_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_YEAR_POSITION.key, { value:defCfg.MY_LABEL_YEAR_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The suffix used after the month when rendering the Calendar header
+ * @config MY_LABEL_MONTH_SUFFIX
+ * @type String
+ * @default " "
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_MONTH_SUFFIX.key, { value:defCfg.MY_LABEL_MONTH_SUFFIX.value, handler:this.configLocale } );
+
+ /**
+ * The suffix used after the year when rendering the Calendar header
+ * @config MY_LABEL_YEAR_SUFFIX
+ * @type String
+ * @default ""
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_YEAR_SUFFIX.key, { value:defCfg.MY_LABEL_YEAR_SUFFIX.value, handler:this.configLocale } );
+
+ /**
+ * Configuration for the Month/Year CalendarNavigator UI which allows the user to jump directly to a
+ * specific Month/Year without having to scroll sequentially through months.
+ *
+ * Setting this property to null (default value) or false, will disable the CalendarNavigator UI.
+ *
+ *
+ * Setting this property to true will enable the CalendarNavigatior UI with the default CalendarNavigator configuration values.
+ *
+ *
+ * This property can also be set to an object literal containing configuration properties for the CalendarNavigator UI.
+ * The configuration object expects the the following case-sensitive properties, with the "strings" property being a nested object.
+ * Any properties which are not provided will use the default values (defined in the CalendarNavigator class).
+ *
+ *
+ *
strings
+ *
Object : An object with the properties shown below, defining the string labels to use in the Navigator's UI
+ *
+ *
month
String : The string to use for the month label. Defaults to "Month".
+ *
year
String : The string to use for the year label. Defaults to "Year".
+ *
submit
String : The string to use for the submit button label. Defaults to "Okay".
+ *
cancel
String : The string to use for the cancel button label. Defaults to "Cancel".
+ *
invalidYear
String : The string to use for invalid year values. Defaults to "Year needs to be a number".
+ *
+ *
+ *
monthFormat
String : The month format to use. Either YAHOO.widget.Calendar.LONG, or YAHOO.widget.Calendar.SHORT. Defaults to YAHOO.widget.Calendar.LONG
+ *
initialFocus
String : Either "year" or "month" specifying which input control should get initial focus. Defaults to "year"
';
+
+ return html;
+ },
+
+ /**
+ * Renders the calendar body.
+ * @method renderBody
+ * @param {Date} workingDate The current working Date being used for the render process
+ * @param {Array} html The current working HTML array
+ * @return {Array} The current working HTML array
+ */
+ renderBody : function(workingDate, html) {
+ this.logger.log("Rendering body", "render");
+
+ var DM = YAHOO.widget.DateMath,
+ CAL = YAHOO.widget.Calendar,
+ D = YAHOO.util.Dom,
+ defCfg = CAL._DEFAULT_CONFIG;
+
+ var startDay = this.cfg.getProperty(defCfg.START_WEEKDAY.key);
+
+ this.preMonthDays = workingDate.getDay();
+ if (startDay > 0) {
+ this.preMonthDays -= startDay;
+ }
+ if (this.preMonthDays < 0) {
+ this.preMonthDays += 7;
+ }
+
+ this.monthDays = DM.findMonthEnd(workingDate).getDate();
+ this.postMonthDays = CAL.DISPLAY_DAYS-this.preMonthDays-this.monthDays;
+
+ this.logger.log(this.preMonthDays + " preciding out-of-month days", "render");
+ this.logger.log(this.monthDays + " month days", "render");
+ this.logger.log(this.postMonthDays + " post-month days", "render");
+
+ workingDate = DM.subtract(workingDate, DM.DAY, this.preMonthDays);
+ this.logger.log("Calendar page starts on " + workingDate, "render");
+
+ var weekNum,
+ weekClass,
+ weekPrefix = "w",
+ cellPrefix = "_cell",
+ workingDayPrefix = "wd",
+ dayPrefix = "d",
+ cellRenderers,
+ renderer,
+ todayYear = this.today.getFullYear(),
+ todayMonth = this.today.getMonth(),
+ todayDate = this.today.getDate(),
+ useDate = this.cfg.getProperty(defCfg.PAGEDATE.key),
+ hideBlankWeeks = this.cfg.getProperty(defCfg.HIDE_BLANK_WEEKS.key),
+ showWeekFooter = this.cfg.getProperty(defCfg.SHOW_WEEK_FOOTER.key),
+ showWeekHeader = this.cfg.getProperty(defCfg.SHOW_WEEK_HEADER.key),
+ mindate = this.cfg.getProperty(defCfg.MINDATE.key),
+ maxdate = this.cfg.getProperty(defCfg.MAXDATE.key);
+
+ if (mindate) {
+ mindate = DM.clearTime(mindate);
+ }
+ if (maxdate) {
+ maxdate = DM.clearTime(maxdate);
+ }
+
+ html[html.length] = '';
+
+ var i = 0,
+ tempDiv = document.createElement("div"),
+ cell = document.createElement("td");
+
+ tempDiv.appendChild(cell);
+
+ var cal = this.parent || this;
+
+ for (var r=0;r<6;r++) {
+ weekNum = DM.getWeekNumber(workingDate, startDay);
+ weekClass = weekPrefix + weekNum;
+
+ // Local OOM check for performance, since we already have pagedate
+ if (r !== 0 && hideBlankWeeks === true && workingDate.getMonth() != useDate.getMonth()) {
+ break;
+ } else {
+ html[html.length] = '
';
+
+ if (showWeekHeader) { html = this.renderRowHeader(weekNum, html); }
+
+ for (var d=0; d < 7; d++){ // Render actual days
+
+ cellRenderers = [];
+
+ this.clearElement(cell);
+ cell.className = this.Style.CSS_CELL;
+ cell.id = this.id + cellPrefix + i;
+ this.logger.log("Rendering cell " + cell.id + " (" + workingDate.getFullYear() + "-" + (workingDate.getMonth()+1) + "-" + workingDate.getDate() + ")", "cellrender");
+
+ if (workingDate.getDate() == todayDate &&
+ workingDate.getMonth() == todayMonth &&
+ workingDate.getFullYear() == todayYear) {
+ cellRenderers[cellRenderers.length]=cal.renderCellStyleToday;
+ }
+
+ var workingArray = [workingDate.getFullYear(),workingDate.getMonth()+1,workingDate.getDate()];
+ this.cellDates[this.cellDates.length] = workingArray; // Add this date to cellDates
+
+ // Local OOM check for performance, since we already have pagedate
+ if (workingDate.getMonth() != useDate.getMonth()) {
+ cellRenderers[cellRenderers.length]=cal.renderCellNotThisMonth;
+ } else {
+ D.addClass(cell, workingDayPrefix + workingDate.getDay());
+ D.addClass(cell, dayPrefix + workingDate.getDate());
+
+ for (var s=0;s= d1.getTime() && workingDate.getTime() <= d2.getTime()) {
+ renderer = rArray[2];
+
+ if (workingDate.getTime()==d2.getTime()) {
+ this.renderStack.splice(s,1);
+ }
+ }
+ break;
+ case CAL.WEEKDAY:
+ var weekday = rArray[1][0];
+ if (workingDate.getDay()+1 == weekday) {
+ renderer = rArray[2];
+ }
+ break;
+ case CAL.MONTH:
+ month = rArray[1][0];
+ if (workingDate.getMonth()+1 == month) {
+ renderer = rArray[2];
+ }
+ break;
+ }
+
+ if (renderer) {
+ cellRenderers[cellRenderers.length]=renderer;
+ }
+ }
+
+ }
+
+ if (this._indexOfSelectedFieldArray(workingArray) > -1) {
+ cellRenderers[cellRenderers.length]=cal.renderCellStyleSelected;
+ }
+
+ if ((mindate && (workingDate.getTime() < mindate.getTime())) ||
+ (maxdate && (workingDate.getTime() > maxdate.getTime()))
+ ) {
+ cellRenderers[cellRenderers.length]=cal.renderOutOfBoundsDate;
+ } else {
+ cellRenderers[cellRenderers.length]=cal.styleCellDefault;
+ cellRenderers[cellRenderers.length]=cal.renderCellDefault;
+ }
+
+ for (var x=0; x < cellRenderers.length; ++x) {
+ this.logger.log("renderer[" + x + "] for (" + workingDate.getFullYear() + "-" + (workingDate.getMonth()+1) + "-" + workingDate.getDate() + ")", "cellrender");
+ if (cellRenderers[x].call(cal, workingDate, cell) == CAL.STOP_RENDER) {
+ break;
+ }
+ }
+
+ workingDate.setTime(workingDate.getTime() + DM.ONE_DAY_MS);
+ // Just in case we crossed DST/Summertime boundaries
+ workingDate = DM.clearTime(workingDate);
+
+ if (i >= 0 && i <= 6) {
+ D.addClass(cell, this.Style.CSS_CELL_TOP);
+ }
+ if ((i % 7) === 0) {
+ D.addClass(cell, this.Style.CSS_CELL_LEFT);
+ }
+ if (((i+1) % 7) === 0) {
+ D.addClass(cell, this.Style.CSS_CELL_RIGHT);
+ }
+
+ var postDays = this.postMonthDays;
+ if (hideBlankWeeks && postDays >= 7) {
+ var blankWeeks = Math.floor(postDays/7);
+ for (var p=0;p= ((this.preMonthDays+postDays+this.monthDays)-7)) {
+ D.addClass(cell, this.Style.CSS_CELL_BOTTOM);
+ }
+
+ html[html.length] = tempDiv.innerHTML;
+ i++;
+ }
+
+ if (showWeekFooter) { html = this.renderRowFooter(weekNum, html); }
+
+ html[html.length] = '
';
+ }
+ }
+
+ html[html.length] = '';
+
+ return html;
+ },
+
+ /**
+ * Renders the calendar footer. In the default implementation, there is
+ * no footer.
+ * @method renderFooter
+ * @param {Array} html The current working HTML array
+ * @return {Array} The current working HTML array
+ */
+ renderFooter : function(html) { return html; },
+
+ /**
+ * Renders the calendar after it has been configured. The render() method has a specific call chain that will execute
+ * when the method is called: renderHeader, renderBody, renderFooter.
+ * Refer to the documentation for those methods for information on
+ * individual render tasks.
+ * @method render
+ */
+ render : function() {
+ this.beforeRenderEvent.fire();
+
+ var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG;
+
+ // Find starting day of the current month
+ var workingDate = YAHOO.widget.DateMath.findMonthStart(this.cfg.getProperty(defCfg.PAGEDATE.key));
+
+ this.resetRenderers();
+ this.cellDates.length = 0;
+
+ YAHOO.util.Event.purgeElement(this.oDomContainer, true);
+
+ var html = [];
+
+ html[html.length] = '
';
+ html = this.renderHeader(html);
+ html = this.renderBody(workingDate, html);
+ html = this.renderFooter(html);
+ html[html.length] = '
';
+
+ this.oDomContainer.innerHTML = html.join("\n");
+
+ this.applyListeners();
+ this.cells = this.oDomContainer.getElementsByTagName("td");
+
+ this.cfg.refireEvent(defCfg.TITLE.key);
+ this.cfg.refireEvent(defCfg.CLOSE.key);
+ this.cfg.refireEvent(defCfg.IFRAME.key);
+
+ this.renderEvent.fire();
+ },
+
+ /**
+ * Applies the Calendar's DOM listeners to applicable elements.
+ * @method applyListeners
+ */
+ applyListeners : function() {
+ var root = this.oDomContainer;
+ var cal = this.parent || this;
+ var anchor = "a";
+ var mousedown = "mousedown";
+
+ var linkLeft = YAHOO.util.Dom.getElementsByClassName(this.Style.CSS_NAV_LEFT, anchor, root);
+ var linkRight = YAHOO.util.Dom.getElementsByClassName(this.Style.CSS_NAV_RIGHT, anchor, root);
+
+ if (linkLeft && linkLeft.length > 0) {
+ this.linkLeft = linkLeft[0];
+ YAHOO.util.Event.addListener(this.linkLeft, mousedown, cal.previousMonth, cal, true);
+ }
+
+ if (linkRight && linkRight.length > 0) {
+ this.linkRight = linkRight[0];
+ YAHOO.util.Event.addListener(this.linkRight, mousedown, cal.nextMonth, cal, true);
+ }
+
+ if (cal.cfg.getProperty("navigator") !== null) {
+ this.applyNavListeners();
+ }
+
+ if (this.domEventMap) {
+ var el,elements;
+ for (var cls in this.domEventMap) {
+ if (YAHOO.lang.hasOwnProperty(this.domEventMap, cls)) {
+ var items = this.domEventMap[cls];
+
+ if (! (items instanceof Array)) {
+ items = [items];
+ }
+
+ for (var i=0;i 0) {
+
+ function show(e, obj) {
+ var target = E.getTarget(e);
+ // this == navBtn
+ if (this === target || YAHOO.util.Dom.isAncestor(this, target)) {
+ E.preventDefault(e);
+ }
+ var navigator = calParent.oNavigator;
+ if (navigator) {
+ var pgdate = cal.cfg.getProperty("pagedate");
+ navigator.setYear(pgdate.getFullYear());
+ navigator.setMonth(pgdate.getMonth());
+ navigator.show();
+ }
+ }
+ E.addListener(navBtns, "click", show);
+ }
+ },
+
+ /**
+ * Retrieves the Date object for the specified Calendar cell
+ * @method getDateByCellId
+ * @param {String} id The id of the cell
+ * @return {Date} The Date object for the specified Calendar cell
+ */
+ getDateByCellId : function(id) {
+ var date = this.getDateFieldsByCellId(id);
+ return YAHOO.widget.DateMath.getDate(date[0],date[1]-1,date[2]);
+ },
+
+ /**
+ * Retrieves the Date object for the specified Calendar cell
+ * @method getDateFieldsByCellId
+ * @param {String} id The id of the cell
+ * @return {Array} The array of Date fields for the specified Calendar cell
+ */
+ getDateFieldsByCellId : function(id) {
+ id = id.toLowerCase().split("_cell")[1];
+ id = parseInt(id, 10);
+ return this.cellDates[id];
+ },
+
+ /**
+ * Find the Calendar's cell index for a given date.
+ * If the date is not found, the method returns -1.
+ *
+ * The returned index can be used to lookup the cell HTMLElement
+ * using the Calendar's cells array or passed to selectCell to select
+ * cells by index.
+ *
+ *
+ * See cells, selectCell.
+ *
+ * @method getCellIndex
+ * @param {Date} date JavaScript Date object, for which to find a cell index.
+ * @return {Number} The index of the date in Calendars cellDates/cells arrays, or -1 if the date
+ * is not on the curently rendered Calendar page.
+ */
+ getCellIndex : function(date) {
+ var idx = -1;
+ if (date) {
+ var m = date.getMonth(),
+ y = date.getFullYear(),
+ d = date.getDate(),
+ dates = this.cellDates;
+
+ for (var i = 0; i < dates.length; ++i) {
+ var cellDate = dates[i];
+ if (cellDate[0] === y && cellDate[1] === m+1 && cellDate[2] === d) {
+ idx = i;
+ break;
+ }
+ }
+ }
+ return idx;
+ },
+
+ // BEGIN BUILT-IN TABLE CELL RENDERERS
+
+ /**
+ * Renders a cell that falls before the minimum date or after the maximum date.
+ * widget class.
+ * @method renderOutOfBoundsDate
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
+ * should not be terminated
+ */
+ renderOutOfBoundsDate : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_OOB);
+ cell.innerHTML = workingDate.getDate();
+ return YAHOO.widget.Calendar.STOP_RENDER;
+ },
+
+ /**
+ * Renders the row header for a week.
+ * @method renderRowHeader
+ * @param {Number} weekNum The week number of the current row
+ * @param {Array} cell The current working HTML array
+ */
+ renderRowHeader : function(weekNum, html) {
+ html[html.length] = '
' + weekNum + '
';
+ return html;
+ },
+
+ /**
+ * Renders the row footer for a week.
+ * @method renderRowFooter
+ * @param {Number} weekNum The week number of the current row
+ * @param {Array} cell The current working HTML array
+ */
+ renderRowFooter : function(weekNum, html) {
+ html[html.length] = '
' + weekNum + '
';
+ return html;
+ },
+
+ /**
+ * Renders a single standard calendar cell in the calendar widget table.
+ * All logic for determining how a standard default cell will be rendered is
+ * encapsulated in this method, and must be accounted for when extending the
+ * widget class.
+ * @method renderCellDefault
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ */
+ renderCellDefault : function(workingDate, cell) {
+ cell.innerHTML = '' + this.buildDayLabel(workingDate) + "";
+ },
+
+ /**
+ * Styles a selectable cell.
+ * @method styleCellDefault
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ */
+ styleCellDefault : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_SELECTABLE);
+ },
+
+
+ /**
+ * Renders a single standard calendar cell using the CSS hightlight1 style
+ * @method renderCellStyleHighlight1
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ */
+ renderCellStyleHighlight1 : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT1);
+ },
+
+ /**
+ * Renders a single standard calendar cell using the CSS hightlight2 style
+ * @method renderCellStyleHighlight2
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ */
+ renderCellStyleHighlight2 : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT2);
+ },
+
+ /**
+ * Renders a single standard calendar cell using the CSS hightlight3 style
+ * @method renderCellStyleHighlight3
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ */
+ renderCellStyleHighlight3 : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT3);
+ },
+
+ /**
+ * Renders a single standard calendar cell using the CSS hightlight4 style
+ * @method renderCellStyleHighlight4
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ */
+ renderCellStyleHighlight4 : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT4);
+ },
+
+ /**
+ * Applies the default style used for rendering today's date to the current calendar cell
+ * @method renderCellStyleToday
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ */
+ renderCellStyleToday : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_TODAY);
+ },
+
+ /**
+ * Applies the default style used for rendering selected dates to the current calendar cell
+ * @method renderCellStyleSelected
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
+ * should not be terminated
+ */
+ renderCellStyleSelected : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_SELECTED);
+ },
+
+ /**
+ * Applies the default style used for rendering dates that are not a part of the current
+ * month (preceding or trailing the cells for the current month)
+ * @method renderCellNotThisMonth
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
+ * should not be terminated
+ */
+ renderCellNotThisMonth : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_OOM);
+ cell.innerHTML=workingDate.getDate();
+ return YAHOO.widget.Calendar.STOP_RENDER;
+ },
+
+ /**
+ * Renders the current calendar cell as a non-selectable "black-out" date using the default
+ * restricted style.
+ * @method renderBodyCellRestricted
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
+ * should not be terminated
+ */
+ renderBodyCellRestricted : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL);
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_RESTRICTED);
+ cell.innerHTML=workingDate.getDate();
+ return YAHOO.widget.Calendar.STOP_RENDER;
+ },
+
+ // END BUILT-IN TABLE CELL RENDERERS
+
+ // BEGIN MONTH NAVIGATION METHODS
+
+ /**
+ * Adds the designated number of months to the current calendar month, and sets the current
+ * calendar page date to the new month.
+ * @method addMonths
+ * @param {Number} count The number of months to add to the current calendar
+ */
+ addMonths : function(count) {
+ var cfgPageDate = YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;
+ this.cfg.setProperty(cfgPageDate, YAHOO.widget.DateMath.add(this.cfg.getProperty(cfgPageDate), YAHOO.widget.DateMath.MONTH, count));
+ this.resetRenderers();
+ this.changePageEvent.fire();
+ },
+
+ /**
+ * Subtracts the designated number of months from the current calendar month, and sets the current
+ * calendar page date to the new month.
+ * @method subtractMonths
+ * @param {Number} count The number of months to subtract from the current calendar
+ */
+ subtractMonths : function(count) {
+ var cfgPageDate = YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;
+ this.cfg.setProperty(cfgPageDate, YAHOO.widget.DateMath.subtract(this.cfg.getProperty(cfgPageDate), YAHOO.widget.DateMath.MONTH, count));
+ this.resetRenderers();
+ this.changePageEvent.fire();
+ },
+
+ /**
+ * Adds the designated number of years to the current calendar, and sets the current
+ * calendar page date to the new month.
+ * @method addYears
+ * @param {Number} count The number of years to add to the current calendar
+ */
+ addYears : function(count) {
+ var cfgPageDate = YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;
+ this.cfg.setProperty(cfgPageDate, YAHOO.widget.DateMath.add(this.cfg.getProperty(cfgPageDate), YAHOO.widget.DateMath.YEAR, count));
+ this.resetRenderers();
+ this.changePageEvent.fire();
+ },
+
+ /**
+ * Subtcats the designated number of years from the current calendar, and sets the current
+ * calendar page date to the new month.
+ * @method subtractYears
+ * @param {Number} count The number of years to subtract from the current calendar
+ */
+ subtractYears : function(count) {
+ var cfgPageDate = YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;
+ this.cfg.setProperty(cfgPageDate, YAHOO.widget.DateMath.subtract(this.cfg.getProperty(cfgPageDate), YAHOO.widget.DateMath.YEAR, count));
+ this.resetRenderers();
+ this.changePageEvent.fire();
+ },
+
+ /**
+ * Navigates to the next month page in the calendar widget.
+ * @method nextMonth
+ */
+ nextMonth : function() {
+ this.addMonths(1);
+ },
+
+ /**
+ * Navigates to the previous month page in the calendar widget.
+ * @method previousMonth
+ */
+ previousMonth : function() {
+ this.subtractMonths(1);
+ },
+
+ /**
+ * Navigates to the next year in the currently selected month in the calendar widget.
+ * @method nextYear
+ */
+ nextYear : function() {
+ this.addYears(1);
+ },
+
+ /**
+ * Navigates to the previous year in the currently selected month in the calendar widget.
+ * @method previousYear
+ */
+ previousYear : function() {
+ this.subtractYears(1);
+ },
+
+ // END MONTH NAVIGATION METHODS
+
+ // BEGIN SELECTION METHODS
+
+ /**
+ * Resets the calendar widget to the originally selected month and year, and
+ * sets the calendar to the initial selection(s).
+ * @method reset
+ */
+ reset : function() {
+ var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG;
+ this.cfg.resetProperty(defCfg.SELECTED.key);
+ this.cfg.resetProperty(defCfg.PAGEDATE.key);
+ this.resetEvent.fire();
+ },
+
+ /**
+ * Clears the selected dates in the current calendar widget and sets the calendar
+ * to the current month and year.
+ * @method clear
+ */
+ clear : function() {
+ var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG;
+ this.cfg.setProperty(defCfg.SELECTED.key, []);
+ this.cfg.setProperty(defCfg.PAGEDATE.key, new Date(this.today.getTime()));
+ this.clearEvent.fire();
+ },
+
+ /**
+ * Selects a date or a collection of dates on the current calendar. This method, by default,
+ * does not call the render method explicitly. Once selection has completed, render must be
+ * called for the changes to be reflected visually.
+ *
+ * Any dates which are OOB (out of bounds, not selectable) will not be selected and the array of
+ * selected dates passed to the selectEvent will not contain OOB dates.
+ *
+ * If all dates are OOB, the no state change will occur; beforeSelect and select events will not be fired.
+ *
+ * @method select
+ * @param {String/Date/Date[]} date The date string of dates to select in the current calendar. Valid formats are
+ * individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
+ * Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
+ * This method can also take a JavaScript Date object or an array of Date objects.
+ * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
+ */
+ select : function(date) {
+ this.logger.log("Select: " + date, "info");
+
+ var aToBeSelected = this._toFieldArray(date);
+ this.logger.log("Selection field array: " + aToBeSelected, "info");
+
+ // Filtered array of valid dates
+ var validDates = [];
+ var selected = [];
+ var cfgSelected = YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key;
+
+ for (var a=0; a < aToBeSelected.length; ++a) {
+ var toSelect = aToBeSelected[a];
+
+ if (!this.isDateOOB(this._toDate(toSelect))) {
+
+ if (validDates.length === 0) {
+ this.beforeSelectEvent.fire();
+ selected = this.cfg.getProperty(cfgSelected);
+ }
+
+ validDates.push(toSelect);
+
+ if (this._indexOfSelectedFieldArray(toSelect) == -1) {
+ selected[selected.length] = toSelect;
+ }
+ }
+ }
+
+ if (validDates.length === 0) { this.logger.log("All provided dates were OOB. beforeSelect and select events not fired", "info"); }
+
+ if (validDates.length > 0) {
+ if (this.parent) {
+ this.parent.cfg.setProperty(cfgSelected, selected);
+ } else {
+ this.cfg.setProperty(cfgSelected, selected);
+ }
+ this.selectEvent.fire(validDates);
+ }
+
+ return this.getSelectedDates();
+ },
+
+ /**
+ * Selects a date on the current calendar by referencing the index of the cell that should be selected.
+ * This method is used to easily select a single cell (usually with a mouse click) without having to do
+ * a full render. The selected style is applied to the cell directly.
+ *
+ * If the cell is not marked with the CSS_CELL_SELECTABLE class (as is the case by default for out of month
+ * or out of bounds cells), it will not be selected and in such a case beforeSelect and select events will not be fired.
+ *
+ * @method selectCell
+ * @param {Number} cellIndex The index of the cell to select in the current calendar.
+ * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
+ */
+ selectCell : function(cellIndex) {
+
+ var cell = this.cells[cellIndex];
+ var cellDate = this.cellDates[cellIndex];
+ var dCellDate = this._toDate(cellDate);
+ this.logger.log("Select: " + dCellDate, "info");
+
+ var selectable = YAHOO.util.Dom.hasClass(cell, this.Style.CSS_CELL_SELECTABLE);
+ if (!selectable) {this.logger.log("The cell at cellIndex:" + cellIndex + " is not a selectable cell. beforeSelect, select events not fired", "info"); }
+
+ if (selectable) {
+
+ this.beforeSelectEvent.fire();
+
+ var cfgSelected = YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key;
+ var selected = this.cfg.getProperty(cfgSelected);
+
+ var selectDate = cellDate.concat();
+
+ if (this._indexOfSelectedFieldArray(selectDate) == -1) {
+ selected[selected.length] = selectDate;
+ }
+ if (this.parent) {
+ this.parent.cfg.setProperty(cfgSelected, selected);
+ } else {
+ this.cfg.setProperty(cfgSelected, selected);
+ }
+ this.renderCellStyleSelected(dCellDate,cell);
+ this.selectEvent.fire([selectDate]);
+
+ this.doCellMouseOut.call(cell, null, this);
+ }
+
+ return this.getSelectedDates();
+ },
+
+ /**
+ * Deselects a date or a collection of dates on the current calendar. This method, by default,
+ * does not call the render method explicitly. Once deselection has completed, render must be
+ * called for the changes to be reflected visually.
+ *
+ * The method will not attempt to deselect any dates which are OOB (out of bounds, and hence not selectable)
+ * and the array of deselected dates passed to the deselectEvent will not contain any OOB dates.
+ *
+ * If all dates are OOB, beforeDeselect and deselect events will not be fired.
+ *
+ * @method deselect
+ * @param {String/Date/Date[]} date The date string of dates to deselect in the current calendar. Valid formats are
+ * individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
+ * Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
+ * This method can also take a JavaScript Date object or an array of Date objects.
+ * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
+ */
+ deselect : function(date) {
+ this.logger.log("Deselect: " + date, "info");
+
+ var aToBeDeselected = this._toFieldArray(date);
+ this.logger.log("Deselection field array: " + aToBeDeselected, "info");
+
+ var validDates = [];
+ var selected = [];
+ var cfgSelected = YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key;
+
+ for (var a=0; a < aToBeDeselected.length; ++a) {
+ var toDeselect = aToBeDeselected[a];
+
+ if (!this.isDateOOB(this._toDate(toDeselect))) {
+
+ if (validDates.length === 0) {
+ this.beforeDeselectEvent.fire();
+ selected = this.cfg.getProperty(cfgSelected);
+ }
+
+ validDates.push(toDeselect);
+
+ var index = this._indexOfSelectedFieldArray(toDeselect);
+ if (index != -1) {
+ selected.splice(index,1);
+ }
+ }
+ }
+
+ if (validDates.length === 0) { this.logger.log("All provided dates were OOB. beforeDeselect and deselect events not fired");}
+
+ if (validDates.length > 0) {
+ if (this.parent) {
+ this.parent.cfg.setProperty(cfgSelected, selected);
+ } else {
+ this.cfg.setProperty(cfgSelected, selected);
+ }
+ this.deselectEvent.fire(validDates);
+ }
+
+ return this.getSelectedDates();
+ },
+
+ /**
+ * Deselects a date on the current calendar by referencing the index of the cell that should be deselected.
+ * This method is used to easily deselect a single cell (usually with a mouse click) without having to do
+ * a full render. The selected style is removed from the cell directly.
+ *
+ * If the cell is not marked with the CSS_CELL_SELECTABLE class (as is the case by default for out of month
+ * or out of bounds cells), the method will not attempt to deselect it and in such a case, beforeDeselect and
+ * deselect events will not be fired.
+ *
+ * @method deselectCell
+ * @param {Number} cellIndex The index of the cell to deselect in the current calendar.
+ * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
+ */
+ deselectCell : function(cellIndex) {
+ var cell = this.cells[cellIndex];
+ var cellDate = this.cellDates[cellIndex];
+ var cellDateIndex = this._indexOfSelectedFieldArray(cellDate);
+
+ var selectable = YAHOO.util.Dom.hasClass(cell, this.Style.CSS_CELL_SELECTABLE);
+ if (!selectable) { this.logger.log("The cell at cellIndex:" + cellIndex + " is not a selectable/deselectable cell", "info"); }
+
+ if (selectable) {
+
+ this.beforeDeselectEvent.fire();
+
+ var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG;
+ var selected = this.cfg.getProperty(defCfg.SELECTED.key);
+
+ var dCellDate = this._toDate(cellDate);
+ var selectDate = cellDate.concat();
+
+ if (cellDateIndex > -1) {
+ if (this.cfg.getProperty(defCfg.PAGEDATE.key).getMonth() == dCellDate.getMonth() &&
+ this.cfg.getProperty(defCfg.PAGEDATE.key).getFullYear() == dCellDate.getFullYear()) {
+ YAHOO.util.Dom.removeClass(cell, this.Style.CSS_CELL_SELECTED);
+ }
+ selected.splice(cellDateIndex, 1);
+ }
+
+ if (this.parent) {
+ this.parent.cfg.setProperty(defCfg.SELECTED.key, selected);
+ } else {
+ this.cfg.setProperty(defCfg.SELECTED.key, selected);
+ }
+
+ this.deselectEvent.fire(selectDate);
+ }
+
+ return this.getSelectedDates();
+ },
+
+ /**
+ * Deselects all dates on the current calendar.
+ * @method deselectAll
+ * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
+ * Assuming that this function executes properly, the return value should be an empty array.
+ * However, the empty array is returned for the sake of being able to check the selection status
+ * of the calendar.
+ */
+ deselectAll : function() {
+ this.beforeDeselectEvent.fire();
+
+ var cfgSelected = YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key;
+
+ var selected = this.cfg.getProperty(cfgSelected);
+ var count = selected.length;
+ var sel = selected.concat();
+
+ if (this.parent) {
+ this.parent.cfg.setProperty(cfgSelected, []);
+ } else {
+ this.cfg.setProperty(cfgSelected, []);
+ }
+
+ if (count > 0) {
+ this.deselectEvent.fire(sel);
+ }
+
+ return this.getSelectedDates();
+ },
+
+ // END SELECTION METHODS
+
+ // BEGIN TYPE CONVERSION METHODS
+
+ /**
+ * Converts a date (either a JavaScript Date object, or a date string) to the internal data structure
+ * used to represent dates: [[yyyy,mm,dd],[yyyy,mm,dd]].
+ * @method _toFieldArray
+ * @private
+ * @param {String/Date/Date[]} date The date string of dates to deselect in the current calendar. Valid formats are
+ * individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
+ * Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
+ * This method can also take a JavaScript Date object or an array of Date objects.
+ * @return {Array[](Number[])} Array of date field arrays
+ */
+ _toFieldArray : function(date) {
+ var returnDate = [];
+
+ if (date instanceof Date) {
+ returnDate = [[date.getFullYear(), date.getMonth()+1, date.getDate()]];
+ } else if (YAHOO.lang.isString(date)) {
+ returnDate = this._parseDates(date);
+ } else if (YAHOO.lang.isArray(date)) {
+ for (var i=0;i maxDate.getTime()));
+ },
+
+ /**
+ * Parses a pagedate configuration property value. The value can either be specified as a string of form "mm/yyyy" or a Date object
+ * and is parsed into a Date object normalized to the first day of the month. If no value is passed in, the month and year from today's date are used to create the Date object
+ * @method _parsePageDate
+ * @private
+ * @param {Date|String} date Pagedate value which needs to be parsed
+ * @return {Date} The Date object representing the pagedate
+ */
+ _parsePageDate : function(date) {
+ var parsedDate;
+
+ var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG;
+
+ if (date) {
+ if (date instanceof Date) {
+ parsedDate = YAHOO.widget.DateMath.findMonthStart(date);
+ } else {
+ var month, year, aMonthYear;
+ aMonthYear = date.split(this.cfg.getProperty(defCfg.DATE_FIELD_DELIMITER.key));
+ month = parseInt(aMonthYear[this.cfg.getProperty(defCfg.MY_MONTH_POSITION.key)-1], 10)-1;
+ year = parseInt(aMonthYear[this.cfg.getProperty(defCfg.MY_YEAR_POSITION.key)-1], 10);
+
+ parsedDate = YAHOO.widget.DateMath.getDate(year, month, 1);
+ }
+ } else {
+ parsedDate = YAHOO.widget.DateMath.getDate(this.today.getFullYear(), this.today.getMonth(), 1);
+ }
+ return parsedDate;
+ },
+
+ // END UTILITY METHODS
+
+ // BEGIN EVENT HANDLERS
+
+ /**
+ * Event executed before a date is selected in the calendar widget.
+ * @deprecated Event handlers for this event should be susbcribed to beforeSelectEvent.
+ */
+ onBeforeSelect : function() {
+ if (this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.MULTI_SELECT.key) === false) {
+ if (this.parent) {
+ this.parent.callChildFunction("clearAllBodyCellStyles", this.Style.CSS_CELL_SELECTED);
+ this.parent.deselectAll();
+ } else {
+ this.clearAllBodyCellStyles(this.Style.CSS_CELL_SELECTED);
+ this.deselectAll();
+ }
+ }
+ },
+
+ /**
+ * Event executed when a date is selected in the calendar widget.
+ * @param {Array} selected An array of date field arrays representing which date or dates were selected. Example: [ [2006,8,6],[2006,8,7],[2006,8,8] ]
+ * @deprecated Event handlers for this event should be susbcribed to selectEvent.
+ */
+ onSelect : function(selected) { },
+
+ /**
+ * Event executed before a date is deselected in the calendar widget.
+ * @deprecated Event handlers for this event should be susbcribed to beforeDeselectEvent.
+ */
+ onBeforeDeselect : function() { },
+
+ /**
+ * Event executed when a date is deselected in the calendar widget.
+ * @param {Array} selected An array of date field arrays representing which date or dates were deselected. Example: [ [2006,8,6],[2006,8,7],[2006,8,8] ]
+ * @deprecated Event handlers for this event should be susbcribed to deselectEvent.
+ */
+ onDeselect : function(deselected) { },
+
+ /**
+ * Event executed when the user navigates to a different calendar page.
+ * @deprecated Event handlers for this event should be susbcribed to changePageEvent.
+ */
+ onChangePage : function() {
+ this.render();
+ },
+
+ /**
+ * Event executed when the calendar widget is rendered.
+ * @deprecated Event handlers for this event should be susbcribed to renderEvent.
+ */
+ onRender : function() { },
+
+ /**
+ * Event executed when the calendar widget is reset to its original state.
+ * @deprecated Event handlers for this event should be susbcribed to resetEvemt.
+ */
+ onReset : function() { this.render(); },
+
+ /**
+ * Event executed when the calendar widget is completely cleared to the current month with no selections.
+ * @deprecated Event handlers for this event should be susbcribed to clearEvent.
+ */
+ onClear : function() { this.render(); },
+
+ /**
+ * Validates the calendar widget. This method has no default implementation
+ * and must be extended by subclassing the widget.
+ * @return Should return true if the widget validates, and false if
+ * it doesn't.
+ * @type Boolean
+ */
+ validate : function() { return true; },
+
+ // END EVENT HANDLERS
+
+ // BEGIN DATE PARSE METHODS
+
+ /**
+ * Converts a date string to a date field array
+ * @private
+ * @param {String} sDate Date string. Valid formats are mm/dd and mm/dd/yyyy.
+ * @return A date field array representing the string passed to the method
+ * @type Array[](Number[])
+ */
+ _parseDate : function(sDate) {
+ var aDate = sDate.split(this.Locale.DATE_FIELD_DELIMITER);
+ var rArray;
+
+ if (aDate.length == 2) {
+ rArray = [aDate[this.Locale.MD_MONTH_POSITION-1],aDate[this.Locale.MD_DAY_POSITION-1]];
+ rArray.type = YAHOO.widget.Calendar.MONTH_DAY;
+ } else {
+ rArray = [aDate[this.Locale.MDY_YEAR_POSITION-1],aDate[this.Locale.MDY_MONTH_POSITION-1],aDate[this.Locale.MDY_DAY_POSITION-1]];
+ rArray.type = YAHOO.widget.Calendar.DATE;
+ }
+
+ for (var i=0;i
+*
+*
+*
+* The tables for the calendars ("cal1_0" and "cal1_1") will be inserted into those containers.
+*
+*
+* NOTE: As of 2.4.0, the constructor's ID argument is optional.
+* The CalendarGroup can be constructed by simply providing a container ID string,
+* or a reference to a container DIV HTMLElement (the element needs to exist
+* in the document).
+*
+* E.g.:
+*
+* var c = new YAHOO.widget.CalendarGroup("calContainer", configOptions);
+*
+* or:
+*
+* var containerDiv = YAHOO.util.Dom.get("calContainer");
+* var c = new YAHOO.widget.CalendarGroup(containerDiv, configOptions);
+*
+*
+*
+* If not provided, the ID will be generated from the container DIV ID by adding an "_t" suffix.
+* For example if an ID is not provided, and the container's ID is "calContainer", the CalendarGroup's ID will be set to "calContainer_t".
+*
+*
+* @namespace YAHOO.widget
+* @class CalendarGroup
+* @constructor
+* @param {String} id optional The id of the table element that will represent the CalendarGroup widget. As of 2.4.0, this argument is optional.
+* @param {String | HTMLElement} container The id of the container div element that will wrap the CalendarGroup table, or a reference to a DIV element which exists in the document.
+* @param {Object} config optional The configuration object containing the initial configuration values for the CalendarGroup.
+*/
+YAHOO.widget.CalendarGroup = function(id, containerId, config) {
+ if (arguments.length > 0) {
+ this.init.apply(this, arguments);
+ }
+};
+
+YAHOO.widget.CalendarGroup.prototype = {
+
+ /**
+ * Initializes the calendar group. All subclasses must call this method in order for the
+ * group to be initialized properly.
+ * @method init
+ * @param {String} id optional The id of the table element that will represent the CalendarGroup widget. As of 2.4.0, this argument is optional.
+ * @param {String | HTMLElement} container The id of the container div element that will wrap the CalendarGroup table, or a reference to a DIV element which exists in the document.
+ * @param {Object} config optional The configuration object containing the initial configuration values for the CalendarGroup.
+ */
+ init : function(id, container, config) {
+
+ // Normalize 2.4.0, pre 2.4.0 args
+ var nArgs = this._parseArgs(arguments);
+
+ id = nArgs.id;
+ container = nArgs.container;
+ config = nArgs.config;
+
+ this.oDomContainer = YAHOO.util.Dom.get(container);
+ if (!this.oDomContainer) { this.logger.log("Container not found in document.", "error"); }
+
+ if (!this.oDomContainer.id) {
+ this.oDomContainer.id = YAHOO.util.Dom.generateId();
+ }
+ if (!id) {
+ id = this.oDomContainer.id + "_t";
+ }
+
+ /**
+ * The unique id associated with the CalendarGroup
+ * @property id
+ * @type String
+ */
+ this.id = id;
+
+ /**
+ * The unique id associated with the CalendarGroup container
+ * @property containerId
+ * @type String
+ */
+ this.containerId = this.oDomContainer.id;
+
+ this.logger = new YAHOO.widget.LogWriter("CalendarGroup " + this.id);
+ this.initEvents();
+ this.initStyles();
+
+ /**
+ * The collection of Calendar pages contained within the CalendarGroup
+ * @property pages
+ * @type YAHOO.widget.Calendar[]
+ */
+ this.pages = [];
+
+ YAHOO.util.Dom.addClass(this.oDomContainer, YAHOO.widget.CalendarGroup.CSS_CONTAINER);
+ YAHOO.util.Dom.addClass(this.oDomContainer, YAHOO.widget.CalendarGroup.CSS_MULTI_UP);
+
+ /**
+ * The Config object used to hold the configuration variables for the CalendarGroup
+ * @property cfg
+ * @type YAHOO.util.Config
+ */
+ this.cfg = new YAHOO.util.Config(this);
+
+ /**
+ * The local object which contains the CalendarGroup's options
+ * @property Options
+ * @type Object
+ */
+ this.Options = {};
+
+ /**
+ * The local object which contains the CalendarGroup's locale settings
+ * @property Locale
+ * @type Object
+ */
+ this.Locale = {};
+
+ this.setupConfig();
+
+ if (config) {
+ this.cfg.applyConfig(config, true);
+ }
+
+ this.cfg.fireQueue();
+
+ // OPERA HACK FOR MISWRAPPED FLOATS
+ if (YAHOO.env.ua.opera){
+ this.renderEvent.subscribe(this._fixWidth, this, true);
+ this.showEvent.subscribe(this._fixWidth, this, true);
+ }
+
+ this.logger.log("Initialized " + this.pages.length + "-page CalendarGroup", "info");
+ },
+
+ setupConfig : function() {
+
+ var defCfg = YAHOO.widget.CalendarGroup._DEFAULT_CONFIG;
+
+ /**
+ * The number of pages to include in the CalendarGroup. This value can only be set once, in the CalendarGroup's constructor arguments.
+ * @config pages
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.PAGES.key, { value:defCfg.PAGES.value, validator:this.cfg.checkNumber, handler:this.configPages } );
+
+ /**
+ * The month/year representing the current visible Calendar date (mm/yyyy)
+ * @config pagedate
+ * @type String
+ * @default today's date
+ */
+ this.cfg.addProperty(defCfg.PAGEDATE.key, { value:new Date(), handler:this.configPageDate } );
+
+ /**
+ * The date or range of dates representing the current Calendar selection
+ * @config selected
+ * @type String
+ * @default []
+ */
+ this.cfg.addProperty(defCfg.SELECTED.key, { value:[], handler:this.configSelected } );
+
+ /**
+ * The title to display above the CalendarGroup's month header
+ * @config title
+ * @type String
+ * @default ""
+ */
+ this.cfg.addProperty(defCfg.TITLE.key, { value:defCfg.TITLE.value, handler:this.configTitle } );
+
+ /**
+ * Whether or not a close button should be displayed for this CalendarGroup
+ * @config close
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.CLOSE.key, { value:defCfg.CLOSE.value, handler:this.configClose } );
+
+ /**
+ * Whether or not an iframe shim should be placed under the Calendar to prevent select boxes from bleeding through in Internet Explorer 6 and below.
+ * This property is enabled by default for IE6 and below. It is disabled by default for other browsers for performance reasons, but can be
+ * enabled if required.
+ *
+ * @config iframe
+ * @type Boolean
+ * @default true for IE6 and below, false for all other browsers
+ */
+ this.cfg.addProperty(defCfg.IFRAME.key, { value:defCfg.IFRAME.value, handler:this.configIframe, validator:this.cfg.checkBoolean } );
+
+ /**
+ * The minimum selectable date in the current Calendar (mm/dd/yyyy)
+ * @config mindate
+ * @type String
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.MINDATE.key, { value:defCfg.MINDATE.value, handler:this.delegateConfig } );
+
+ /**
+ * The maximum selectable date in the current Calendar (mm/dd/yyyy)
+ * @config maxdate
+ * @type String
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.MAXDATE.key, { value:defCfg.MAXDATE.value, handler:this.delegateConfig } );
+
+ // Options properties
+
+ /**
+ * True if the Calendar should allow multiple selections. False by default.
+ * @config MULTI_SELECT
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.MULTI_SELECT.key, { value:defCfg.MULTI_SELECT.value, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+ /**
+ * The weekday the week begins on. Default is 0 (Sunday).
+ * @config START_WEEKDAY
+ * @type number
+ * @default 0
+ */
+ this.cfg.addProperty(defCfg.START_WEEKDAY.key, { value:defCfg.START_WEEKDAY.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * True if the Calendar should show weekday labels. True by default.
+ * @config SHOW_WEEKDAYS
+ * @type Boolean
+ * @default true
+ */
+ this.cfg.addProperty(defCfg.SHOW_WEEKDAYS.key, { value:defCfg.SHOW_WEEKDAYS.value, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+ /**
+ * True if the Calendar should show week row headers. False by default.
+ * @config SHOW_WEEK_HEADER
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.SHOW_WEEK_HEADER.key,{ value:defCfg.SHOW_WEEK_HEADER.value, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+ /**
+ * True if the Calendar should show week row footers. False by default.
+ * @config SHOW_WEEK_FOOTER
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.SHOW_WEEK_FOOTER.key,{ value:defCfg.SHOW_WEEK_FOOTER.value, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+ /**
+ * True if the Calendar should suppress weeks that are not a part of the current month. False by default.
+ * @config HIDE_BLANK_WEEKS
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.HIDE_BLANK_WEEKS.key,{ value:defCfg.HIDE_BLANK_WEEKS.value, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+ /**
+ * The image that should be used for the left navigation arrow.
+ * @config NAV_ARROW_LEFT
+ * @type String
+ * @deprecated You can customize the image by overriding the default CSS class for the left arrow - "calnavleft"
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.NAV_ARROW_LEFT.key, { value:defCfg.NAV_ARROW_LEFT.value, handler:this.delegateConfig } );
+
+ /**
+ * The image that should be used for the right navigation arrow.
+ * @config NAV_ARROW_RIGHT
+ * @type String
+ * @deprecated You can customize the image by overriding the default CSS class for the right arrow - "calnavright"
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.NAV_ARROW_RIGHT.key, { value:defCfg.NAV_ARROW_RIGHT.value, handler:this.delegateConfig } );
+
+ // Locale properties
+
+ /**
+ * The short month labels for the current locale.
+ * @config MONTHS_SHORT
+ * @type String[]
+ * @default ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+ */
+ this.cfg.addProperty(defCfg.MONTHS_SHORT.key, { value:defCfg.MONTHS_SHORT.value, handler:this.delegateConfig } );
+
+ /**
+ * The long month labels for the current locale.
+ * @config MONTHS_LONG
+ * @type String[]
+ * @default ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
+ */
+ this.cfg.addProperty(defCfg.MONTHS_LONG.key, { value:defCfg.MONTHS_LONG.value, handler:this.delegateConfig } );
+
+ /**
+ * The 1-character weekday labels for the current locale.
+ * @config WEEKDAYS_1CHAR
+ * @type String[]
+ * @default ["S", "M", "T", "W", "T", "F", "S"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_1CHAR.key, { value:defCfg.WEEKDAYS_1CHAR.value, handler:this.delegateConfig } );
+
+ /**
+ * The short weekday labels for the current locale.
+ * @config WEEKDAYS_SHORT
+ * @type String[]
+ * @default ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_SHORT.key, { value:defCfg.WEEKDAYS_SHORT.value, handler:this.delegateConfig } );
+
+ /**
+ * The medium weekday labels for the current locale.
+ * @config WEEKDAYS_MEDIUM
+ * @type String[]
+ * @default ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_MEDIUM.key, { value:defCfg.WEEKDAYS_MEDIUM.value, handler:this.delegateConfig } );
+
+ /**
+ * The long weekday labels for the current locale.
+ * @config WEEKDAYS_LONG
+ * @type String[]
+ * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_LONG.key, { value:defCfg.WEEKDAYS_LONG.value, handler:this.delegateConfig } );
+
+ /**
+ * The setting that determines which length of month labels should be used. Possible values are "short" and "long".
+ * @config LOCALE_MONTHS
+ * @type String
+ * @default "long"
+ */
+ this.cfg.addProperty(defCfg.LOCALE_MONTHS.key, { value:defCfg.LOCALE_MONTHS.value, handler:this.delegateConfig } );
+
+ /**
+ * The setting that determines which length of weekday labels should be used. Possible values are "1char", "short", "medium", and "long".
+ * @config LOCALE_WEEKDAYS
+ * @type String
+ * @default "short"
+ */
+ this.cfg.addProperty(defCfg.LOCALE_WEEKDAYS.key, { value:defCfg.LOCALE_WEEKDAYS.value, handler:this.delegateConfig } );
+
+ /**
+ * The value used to delimit individual dates in a date string passed to various Calendar functions.
+ * @config DATE_DELIMITER
+ * @type String
+ * @default ","
+ */
+ this.cfg.addProperty(defCfg.DATE_DELIMITER.key, { value:defCfg.DATE_DELIMITER.value, handler:this.delegateConfig } );
+
+ /**
+ * The value used to delimit date fields in a date string passed to various Calendar functions.
+ * @config DATE_FIELD_DELIMITER
+ * @type String
+ * @default "/"
+ */
+ this.cfg.addProperty(defCfg.DATE_FIELD_DELIMITER.key,{ value:defCfg.DATE_FIELD_DELIMITER.value, handler:this.delegateConfig } );
+
+ /**
+ * The value used to delimit date ranges in a date string passed to various Calendar functions.
+ * @config DATE_RANGE_DELIMITER
+ * @type String
+ * @default "-"
+ */
+ this.cfg.addProperty(defCfg.DATE_RANGE_DELIMITER.key,{ value:defCfg.DATE_RANGE_DELIMITER.value, handler:this.delegateConfig } );
+
+ /**
+ * The position of the month in a month/year date string
+ * @config MY_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MY_MONTH_POSITION.key, { value:defCfg.MY_MONTH_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the year in a month/year date string
+ * @config MY_YEAR_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MY_YEAR_POSITION.key, { value:defCfg.MY_YEAR_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the month in a month/day date string
+ * @config MD_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MD_MONTH_POSITION.key, { value:defCfg.MD_MONTH_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the day in a month/year date string
+ * @config MD_DAY_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MD_DAY_POSITION.key, { value:defCfg.MD_DAY_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the month in a month/day/year date string
+ * @config MDY_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MDY_MONTH_POSITION.key, { value:defCfg.MDY_MONTH_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the day in a month/day/year date string
+ * @config MDY_DAY_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MDY_DAY_POSITION.key, { value:defCfg.MDY_DAY_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the year in a month/day/year date string
+ * @config MDY_YEAR_POSITION
+ * @type Number
+ * @default 3
+ */
+ this.cfg.addProperty(defCfg.MDY_YEAR_POSITION.key, { value:defCfg.MDY_YEAR_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the month in the month year label string used as the Calendar header
+ * @config MY_LABEL_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_MONTH_POSITION.key, { value:defCfg.MY_LABEL_MONTH_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the year in the month year label string used as the Calendar header
+ * @config MY_LABEL_YEAR_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_YEAR_POSITION.key, { value:defCfg.MY_LABEL_YEAR_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The suffix used after the month when rendering the Calendar header
+ * @config MY_LABEL_MONTH_SUFFIX
+ * @type String
+ * @default " "
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_MONTH_SUFFIX.key, { value:defCfg.MY_LABEL_MONTH_SUFFIX.value, handler:this.delegateConfig } );
+
+ /**
+ * The suffix used after the year when rendering the Calendar header
+ * @config MY_LABEL_YEAR_SUFFIX
+ * @type String
+ * @default ""
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_YEAR_SUFFIX.key, { value:defCfg.MY_LABEL_YEAR_SUFFIX.value, handler:this.delegateConfig } );
+
+ /**
+ * Configuration for the Month Year Navigation UI. By default it is disabled
+ * @config NAV
+ * @type Object
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.NAV.key, { value:defCfg.NAV.value, handler:this.configNavigator } );
+ },
+
+ /**
+ * Initializes CalendarGroup's built-in CustomEvents
+ * @method initEvents
+ */
+ initEvents : function() {
+ var me = this;
+ var strEvent = "Event";
+
+ /**
+ * Proxy subscriber to subscribe to the CalendarGroup's child Calendars' CustomEvents
+ * @method sub
+ * @private
+ * @param {Function} fn The function to subscribe to this CustomEvent
+ * @param {Object} obj The CustomEvent's scope object
+ * @param {Boolean} bOverride Whether or not to apply scope correction
+ */
+ var sub = function(fn, obj, bOverride) {
+ for (var p=0;p 0) ? this.pages[0].cfg.getProperty(cfgSelected) : [];
+ this.cfg.setProperty(cfgSelected, selected, true);
+ },
+
+
+ /**
+ * Delegates a configuration property to the CustomEvents associated with the CalendarGroup's children
+ * @method delegateConfig
+ * @param {String} type The CustomEvent type (usually the property name)
+ * @param {Object[]} args The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+ * @param {Object} obj The scope object. For configuration handlers, this will usually equal the owner.
+ */
+ delegateConfig : function(type, args, obj) {
+ var val = args[0];
+ var cal;
+
+ for (var p=0;p0) {
+ year+=1;
+ }
+ cal.setYear(year);
+ }
+ },
+
+ /**
+ * Calls the render function of all child calendars within the group.
+ * @method render
+ */
+ render : function() {
+ this.renderHeader();
+ for (var p=0;p
+ *
If MULTI_SELECT is false, selectCell will select the cell at the specified index for only the last displayed Calendar page.
+ *
If MULTI_SELECT is true, selectCell will select the cell at the specified index, on each displayed Calendar page.
+ *
+ * @method selectCell
+ * @param {Number} cellIndex The index of the cell to be selected.
+ * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
+ */
+ selectCell : function(cellIndex) {
+ for (var p=0;p=0;--p) {
+ var cal = this.pages[p];
+ cal.previousMonth();
+ }
+ },
+
+ /**
+ * Navigates to the next year in the currently selected month in the calendar widget.
+ * @method nextYear
+ */
+ nextYear : function() {
+ for (var p=0;p 11)) {
+ var DM = YAHOO.widget.DateMath;
+ var newDate = DM.add(date, DM.MONTH, iMonth-date.getMonth());
+ date.setTime(newDate.getTime());
+ } else {
+ date.setMonth(iMonth);
+ }
+ },
+
+ /**
+ * Fixes the width of the CalendarGroup container element, to account for miswrapped floats
+ * @method _fixWidth
+ * @private
+ */
+ _fixWidth : function() {
+ var w = 0;
+ for (var p=0;p 0) {
+ this.oDomContainer.style.width = w + "px";
+ }
+ },
+
+ /**
+ * Returns a string representation of the object.
+ * @method toString
+ * @return {String} A string representation of the CalendarGroup object.
+ */
+ toString : function() {
+ return "CalendarGroup " + this.id;
+ }
+};
+
+/**
+* CSS class representing the container for the calendar
+* @property YAHOO.widget.CalendarGroup.CSS_CONTAINER
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.CalendarGroup.CSS_CONTAINER = "yui-calcontainer";
+
+/**
+* CSS class representing the container for the calendar
+* @property YAHOO.widget.CalendarGroup.CSS_MULTI_UP
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.CalendarGroup.CSS_MULTI_UP = "multi";
+
+/**
+* CSS class representing the title for the 2-up calendar
+* @property YAHOO.widget.CalendarGroup.CSS_2UPTITLE
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.CalendarGroup.CSS_2UPTITLE = "title";
+
+/**
+* CSS class representing the close icon for the 2-up calendar
+* @property YAHOO.widget.CalendarGroup.CSS_2UPCLOSE
+* @static
+* @final
+* @deprecated Along with Calendar.IMG_ROOT and NAV_ARROW_LEFT, NAV_ARROW_RIGHT configuration properties.
+* Calendar's Style.CSS_CLOSE property now represents the CSS class used to render the close icon
+* @type String
+*/
+YAHOO.widget.CalendarGroup.CSS_2UPCLOSE = "close-icon";
+
+YAHOO.lang.augmentProto(YAHOO.widget.CalendarGroup, YAHOO.widget.Calendar, "buildDayLabel",
+ "buildMonthLabel",
+ "renderOutOfBoundsDate",
+ "renderRowHeader",
+ "renderRowFooter",
+ "renderCellDefault",
+ "styleCellDefault",
+ "renderCellStyleHighlight1",
+ "renderCellStyleHighlight2",
+ "renderCellStyleHighlight3",
+ "renderCellStyleHighlight4",
+ "renderCellStyleToday",
+ "renderCellStyleSelected",
+ "renderCellNotThisMonth",
+ "renderBodyCellRestricted",
+ "initStyles",
+ "configTitle",
+ "configClose",
+ "configIframe",
+ "configNavigator",
+ "createTitleBar",
+ "createCloseButton",
+ "removeTitleBar",
+ "removeCloseButton",
+ "hide",
+ "show",
+ "toDate",
+ "_toDate",
+ "_parseArgs",
+ "browser");
+
+/**
+* The set of default Config property keys and values for the CalendarGroup
+* @property YAHOO.widget.CalendarGroup._DEFAULT_CONFIG
+* @final
+* @static
+* @private
+* @type Object
+*/
+YAHOO.widget.CalendarGroup._DEFAULT_CONFIG = YAHOO.widget.Calendar._DEFAULT_CONFIG;
+YAHOO.widget.CalendarGroup._DEFAULT_CONFIG.PAGES = {key:"pages", value:2};
+
+YAHOO.widget.CalGrp = YAHOO.widget.CalendarGroup;
+
+/**
+* @class YAHOO.widget.Calendar2up
+* @extends YAHOO.widget.CalendarGroup
+* @deprecated The old Calendar2up class is no longer necessary, since CalendarGroup renders in a 2up view by default.
+*/
+YAHOO.widget.Calendar2up = function(id, containerId, config) {
+ this.init(id, containerId, config);
+};
+
+YAHOO.extend(YAHOO.widget.Calendar2up, YAHOO.widget.CalendarGroup);
+
+/**
+* @deprecated The old Calendar2up class is no longer necessary, since CalendarGroup renders in a 2up view by default.
+*/
+YAHOO.widget.Cal2up = YAHOO.widget.Calendar2up;
+
+/**
+ * The CalendarNavigator is used along with a Calendar/CalendarGroup to
+ * provide a Month/Year popup navigation control, allowing the user to navigate
+ * to a specific month/year in the Calendar/CalendarGroup without having to
+ * scroll through months sequentially
+ *
+ * @namespace YAHOO.widget
+ * @class CalendarNavigator
+ * @constructor
+ * @param {Calendar|CalendarGroup} cal The instance of the Calendar or CalendarGroup to which this CalendarNavigator should be attached.
+ */
+YAHOO.widget.CalendarNavigator = function(cal) {
+ this.init(cal);
+};
+
+(function() {
+ // Setup static properties (inside anon fn, so that we can use shortcuts)
+ var CN = YAHOO.widget.CalendarNavigator;
+
+ /**
+ * YAHOO.widget.CalendarNavigator.CLASSES contains constants
+ * for the class values applied to the CalendarNaviatgator's
+ * DOM elements
+ * @property YAHOO.widget.CalendarNavigator.CLASSES
+ * @type Object
+ * @static
+ */
+ CN.CLASSES = {
+ /**
+ * Class applied to the Calendar Navigator's bounding box
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.NAV
+ * @type String
+ * @static
+ */
+ NAV :"yui-cal-nav",
+ /**
+ * Class applied to the Calendar/CalendarGroup's bounding box to indicate
+ * the Navigator is currently visible
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.NAV_VISIBLE
+ * @type String
+ * @static
+ */
+ NAV_VISIBLE: "yui-cal-nav-visible",
+ /**
+ * Class applied to the Navigator mask's bounding box
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.MASK
+ * @type String
+ * @static
+ */
+ MASK : "yui-cal-nav-mask",
+ /**
+ * Class applied to the year label/control bounding box
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.YEAR
+ * @type String
+ * @static
+ */
+ YEAR : "yui-cal-nav-y",
+ /**
+ * Class applied to the month label/control bounding box
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.MONTH
+ * @type String
+ * @static
+ */
+ MONTH : "yui-cal-nav-m",
+ /**
+ * Class applied to the submit/cancel button's bounding box
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.BUTTONS
+ * @type String
+ * @static
+ */
+ BUTTONS : "yui-cal-nav-b",
+ /**
+ * Class applied to buttons wrapping element
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.BUTTON
+ * @type String
+ * @static
+ */
+ BUTTON : "yui-cal-nav-btn",
+ /**
+ * Class applied to the validation error area's bounding box
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.ERROR
+ * @type String
+ * @static
+ */
+ ERROR : "yui-cal-nav-e",
+ /**
+ * Class applied to the year input control
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.YEAR_CTRL
+ * @type String
+ * @static
+ */
+ YEAR_CTRL : "yui-cal-nav-yc",
+ /**
+ * Class applied to the month input control
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.MONTH_CTRL
+ * @type String
+ * @static
+ */
+ MONTH_CTRL : "yui-cal-nav-mc",
+ /**
+ * Class applied to controls with invalid data (e.g. a year input field with invalid an year)
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.INVALID
+ * @type String
+ * @static
+ */
+ INVALID : "yui-invalid",
+ /**
+ * Class applied to default controls
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.DEFAULT
+ * @type String
+ * @static
+ */
+ DEFAULT : "yui-default"
+ };
+
+ /**
+ * Object literal containing the default configuration values for the CalendarNavigator
+ * The configuration object is expected to follow the format below, with the properties being
+ * case sensitive.
+ *
+ *
strings
+ *
Object : An object with the properties shown below, defining the string labels to use in the Navigator's UI
+ *
+ *
month
String : The string to use for the month label. Defaults to "Month".
+ *
year
String : The string to use for the year label. Defaults to "Year".
+ *
submit
String : The string to use for the submit button label. Defaults to "Okay".
+ *
cancel
String : The string to use for the cancel button label. Defaults to "Cancel".
+ *
invalidYear
String : The string to use for invalid year values. Defaults to "Year needs to be a number".
+ *
+ *
+ *
monthFormat
String : The month format to use. Either YAHOO.widget.Calendar.LONG, or YAHOO.widget.Calendar.SHORT. Defaults to YAHOO.widget.Calendar.LONG
+ *
initialFocus
String : Either "year" or "month" specifying which input control should get initial focus. Defaults to "year"
+ *
+ * @property _DEFAULT_CFG
+ * @protected
+ * @type Object
+ * @static
+ */
+ CN._DEFAULT_CFG = {
+ strings : {
+ month: "Month",
+ year: "Year",
+ submit: "Okay",
+ cancel: "Cancel",
+ invalidYear : "Year needs to be a number"
+ },
+ monthFormat: YAHOO.widget.Calendar.LONG,
+ initialFocus: "year"
+ };
+
+ /**
+ * The suffix added to the Calendar/CalendarGroup's ID, to generate
+ * a unique ID for the Navigator and it's bounding box.
+ * @property YAHOO.widget.CalendarNavigator.ID_SUFFIX
+ * @static
+ * @type String
+ * @final
+ */
+ CN.ID_SUFFIX = "_nav";
+ /**
+ * The suffix added to the Navigator's ID, to generate
+ * a unique ID for the month control.
+ * @property YAHOO.widget.CalendarNavigator.MONTH_SUFFIX
+ * @static
+ * @type String
+ * @final
+ */
+ CN.MONTH_SUFFIX = "_month";
+ /**
+ * The suffix added to the Navigator's ID, to generate
+ * a unique ID for the year control.
+ * @property YAHOO.widget.CalendarNavigator.YEAR_SUFFIX
+ * @static
+ * @type String
+ * @final
+ */
+ CN.YEAR_SUFFIX = "_year";
+ /**
+ * The suffix added to the Navigator's ID, to generate
+ * a unique ID for the error bounding box.
+ * @property YAHOO.widget.CalendarNavigator.ERROR_SUFFIX
+ * @static
+ * @type String
+ * @final
+ */
+ CN.ERROR_SUFFIX = "_error";
+ /**
+ * The suffix added to the Navigator's ID, to generate
+ * a unique ID for the "Cancel" button.
+ * @property YAHOO.widget.CalendarNavigator.CANCEL_SUFFIX
+ * @static
+ * @type String
+ * @final
+ */
+ CN.CANCEL_SUFFIX = "_cancel";
+ /**
+ * The suffix added to the Navigator's ID, to generate
+ * a unique ID for the "Submit" button.
+ * @property YAHOO.widget.CalendarNavigator.SUBMIT_SUFFIX
+ * @static
+ * @type String
+ * @final
+ */
+ CN.SUBMIT_SUFFIX = "_submit";
+
+ /**
+ * The number of digits to which the year input control is to be limited.
+ * @property YAHOO.widget.CalendarNavigator.YR_MAX_DIGITS
+ * @static
+ * @type Number
+ */
+ CN.YR_MAX_DIGITS = 4;
+
+ /**
+ * The amount by which to increment the current year value,
+ * when the arrow up/down key is pressed on the year control
+ * @property YAHOO.widget.CalendarNavigator.YR_MINOR_INC
+ * @static
+ * @type Number
+ */
+ CN.YR_MINOR_INC = 1;
+
+ /**
+ * The amount by which to increment the current year value,
+ * when the page up/down key is pressed on the year control
+ * @property YAHOO.widget.CalendarNavigator.YR_MAJOR_INC
+ * @static
+ * @type Number
+ */
+ CN.YR_MAJOR_INC = 10;
+
+ /**
+ * Artificial delay (in ms) between the time the Navigator is hidden
+ * and the Calendar/CalendarGroup state is updated. Allows the user
+ * the see the Calendar/CalendarGroup page changing. If set to 0
+ * the Calendar/CalendarGroup page will be updated instantly
+ * @property YAHOO.widget.CalendarNavigator.UPDATE_DELAY
+ * @static
+ * @type Number
+ */
+ CN.UPDATE_DELAY = 50;
+
+ /**
+ * Regular expression used to validate the year input
+ * @property YAHOO.widget.CalendarNavigator.YR_PATTERN
+ * @static
+ * @type RegExp
+ */
+ CN.YR_PATTERN = /^\d+$/;
+ /**
+ * Regular expression used to trim strings
+ * @property YAHOO.widget.CalendarNavigator.TRIM
+ * @static
+ * @type RegExp
+ */
+ CN.TRIM = /^\s*(.*?)\s*$/;
+})();
+
+YAHOO.widget.CalendarNavigator.prototype = {
+
+ /**
+ * The unique ID for this CalendarNavigator instance
+ * @property id
+ * @type String
+ */
+ id : null,
+
+ /**
+ * The Calendar/CalendarGroup instance to which the navigator belongs
+ * @property cal
+ * @type {Calendar|CalendarGroup}
+ */
+ cal : null,
+
+ /**
+ * Reference to the HTMLElement used to render the navigator's bounding box
+ * @property navEl
+ * @type HTMLElement
+ */
+ navEl : null,
+
+ /**
+ * Reference to the HTMLElement used to render the navigator's mask
+ * @property maskEl
+ * @type HTMLElement
+ */
+ maskEl : null,
+
+ /**
+ * Reference to the HTMLElement used to input the year
+ * @property yearEl
+ * @type HTMLElement
+ */
+ yearEl : null,
+
+ /**
+ * Reference to the HTMLElement used to input the month
+ * @property monthEl
+ * @type HTMLElement
+ */
+ monthEl : null,
+
+ /**
+ * Reference to the HTMLElement used to display validation errors
+ * @property errorEl
+ * @type HTMLElement
+ */
+ errorEl : null,
+
+ /**
+ * Reference to the HTMLElement used to update the Calendar/Calendar group
+ * with the month/year values
+ * @property submitEl
+ * @type HTMLElement
+ */
+ submitEl : null,
+
+ /**
+ * Reference to the HTMLElement used to hide the navigator without updating the
+ * Calendar/Calendar group
+ * @property cancelEl
+ * @type HTMLElement
+ */
+ cancelEl : null,
+
+ /**
+ * Reference to the first focusable control in the navigator (by default monthEl)
+ * @property firstCtrl
+ * @type HTMLElement
+ */
+ firstCtrl : null,
+
+ /**
+ * Reference to the last focusable control in the navigator (by default cancelEl)
+ * @property lastCtrl
+ * @type HTMLElement
+ */
+ lastCtrl : null,
+
+ /**
+ * The document containing the Calendar/Calendar group instance
+ * @protected
+ * @property _doc
+ * @type HTMLDocument
+ */
+ _doc : null,
+
+ /**
+ * Internal state property for the current year displayed in the navigator
+ * @protected
+ * @property _year
+ * @type Number
+ */
+ _year: null,
+
+ /**
+ * Internal state property for the current month index displayed in the navigator
+ * @protected
+ * @property _month
+ * @type Number
+ */
+ _month: 0,
+
+ /**
+ * Private internal state property which indicates whether or not the
+ * Navigator has been rendered.
+ * @private
+ * @property __rendered
+ * @type Boolean
+ */
+ __rendered: false,
+
+ /**
+ * Init lifecycle method called as part of construction
+ *
+ * @method init
+ * @param {Calendar} cal The instance of the Calendar or CalendarGroup to which this CalendarNavigator should be attached
+ */
+ init : function(cal) {
+ var calBox = cal.oDomContainer;
+
+ this.cal = cal;
+ this.id = calBox.id + YAHOO.widget.CalendarNavigator.ID_SUFFIX;
+ this._doc = calBox.ownerDocument;
+
+ /**
+ * Private flag, to identify IE6/IE7 Quirks
+ * @private
+ * @property __isIEQuirks
+ */
+ var ie = YAHOO.env.ua.ie;
+ this.__isIEQuirks = (ie && ((ie <= 6) || (ie === 7 && this._doc.compatMode == "BackCompat")));
+ },
+
+ /**
+ * Displays the navigator and mask, updating the input controls to reflect the
+ * currently set month and year. The show method will invoke the render method
+ * if the navigator has not been renderered already, allowing for lazy rendering
+ * of the control.
+ *
+ * The show method will fire the Calendar/CalendarGroup's beforeShowNav and showNav events
+ *
+ * @method show
+ */
+ show : function() {
+ var CLASSES = YAHOO.widget.CalendarNavigator.CLASSES;
+
+ if (this.cal.beforeShowNavEvent.fire()) {
+ if (!this.__rendered) {
+ this.render();
+ }
+ this.clearErrors();
+
+ this._updateMonthUI();
+ this._updateYearUI();
+ this._show(this.navEl, true);
+
+ this.setInitialFocus();
+ this.showMask();
+
+ YAHOO.util.Dom.addClass(this.cal.oDomContainer, CLASSES.NAV_VISIBLE);
+ this.cal.showNavEvent.fire();
+ }
+ },
+
+ /**
+ * Hides the navigator and mask
+ *
+ * The show method will fire the Calendar/CalendarGroup's beforeHideNav event and hideNav events
+ * @method hide
+ */
+ hide : function() {
+ var CLASSES = YAHOO.widget.CalendarNavigator.CLASSES;
+
+ if (this.cal.beforeHideNavEvent.fire()) {
+ this._show(this.navEl, false);
+ this.hideMask();
+ YAHOO.util.Dom.removeClass(this.cal.oDomContainer, CLASSES.NAV_VISIBLE);
+ this.cal.hideNavEvent.fire();
+ }
+ },
+
+
+ /**
+ * Displays the navigator's mask element
+ *
+ * @method showMask
+ */
+ showMask : function() {
+ this._show(this.maskEl, true);
+ if (this.__isIEQuirks) {
+ this._syncMask();
+ }
+ },
+
+ /**
+ * Hides the navigator's mask element
+ *
+ * @method hideMask
+ */
+ hideMask : function() {
+ this._show(this.maskEl, false);
+ },
+
+ /**
+ * Returns the current month set on the navigator
+ *
+ * Note: This may not be the month set in the UI, if
+ * the UI contains an invalid value.
+ *
+ * @method getMonth
+ * @return {Number} The Navigator's current month index
+ */
+ getMonth: function() {
+ return this._month;
+ },
+
+ /**
+ * Returns the current year set on the navigator
+ *
+ * Note: This may not be the year set in the UI, if
+ * the UI contains an invalid value.
+ *
+ * @method getYear
+ * @return {Number} The Navigator's current year value
+ */
+ getYear: function() {
+ return this._year;
+ },
+
+ /**
+ * Sets the current month on the Navigator, and updates the UI
+ *
+ * @method setMonth
+ * @param {Number} nMonth The month index, from 0 (Jan) through 11 (Dec).
+ */
+ setMonth : function(nMonth) {
+ if (nMonth >= 0 && nMonth < 12) {
+ this._month = nMonth;
+ }
+ this._updateMonthUI();
+ },
+
+ /**
+ * Sets the current year on the Navigator, and updates the UI. If the
+ * provided year is invalid, it will not be set.
+ *
+ * @method setYear
+ * @param {Number} nYear The full year value to set the Navigator to.
+ */
+ setYear : function(nYear) {
+ var yrPattern = YAHOO.widget.CalendarNavigator.YR_PATTERN;
+ if (YAHOO.lang.isNumber(nYear) && yrPattern.test(nYear+"")) {
+ this._year = nYear;
+ }
+ this._updateYearUI();
+ },
+
+ /**
+ * Renders the HTML for the navigator, adding it to the
+ * document and attaches event listeners if it has not
+ * already been rendered.
+ *
+ * @method render
+ */
+ render: function() {
+ this.cal.beforeRenderNavEvent.fire();
+ if (!this.__rendered) {
+ this.createNav();
+ this.createMask();
+ this.applyListeners();
+ this.__rendered = true;
+ }
+ this.cal.renderNavEvent.fire();
+ },
+
+ /**
+ * Creates the navigator's containing HTMLElement, it's contents, and appends
+ * the containg element to the Calendar/CalendarGroup's container.
+ *
+ * @method createNav
+ */
+ createNav : function() {
+ var NAV = YAHOO.widget.CalendarNavigator;
+ var doc = this._doc;
+
+ var d = doc.createElement("div");
+ d.className = NAV.CLASSES.NAV;
+
+ var htmlBuf = this.renderNavContents([]);
+
+ d.innerHTML = htmlBuf.join('');
+ this.cal.oDomContainer.appendChild(d);
+
+ this.navEl = d;
+
+ this.yearEl = doc.getElementById(this.id + NAV.YEAR_SUFFIX);
+ this.monthEl = doc.getElementById(this.id + NAV.MONTH_SUFFIX);
+ this.errorEl = doc.getElementById(this.id + NAV.ERROR_SUFFIX);
+ this.submitEl = doc.getElementById(this.id + NAV.SUBMIT_SUFFIX);
+ this.cancelEl = doc.getElementById(this.id + NAV.CANCEL_SUFFIX);
+
+ if (YAHOO.env.ua.gecko && this.yearEl && this.yearEl.type == "text") {
+ // Avoid XUL error on focus, select [ https://bugzilla.mozilla.org/show_bug.cgi?id=236791,
+ // supposedly fixed in 1.8.1, but there are reports of it still being around for methods other than blur ]
+ this.yearEl.setAttribute("autocomplete", "off");
+ }
+
+ this._setFirstLastElements();
+ },
+
+ /**
+ * Creates the Mask HTMLElement and appends it to the Calendar/CalendarGroups
+ * container.
+ *
+ * @method createMask
+ */
+ createMask : function() {
+ var C = YAHOO.widget.CalendarNavigator.CLASSES;
+
+ var d = this._doc.createElement("div");
+ d.className = C.MASK;
+
+ this.cal.oDomContainer.appendChild(d);
+ this.maskEl = d;
+ },
+
+ /**
+ * Used to set the width/height of the mask in pixels to match the Calendar Container.
+ * Currently only used for IE6 and IE7 quirks mode. The other A-Grade browser are handled using CSS (width/height 100%).
+ *
+ * The method is also registered as an HTMLElement resize listener on the Calendars container element.
+ *
+ * @protected
+ * @method _syncMask
+ */
+ _syncMask : function() {
+ var c = this.cal.oDomContainer;
+ if (c && this.maskEl) {
+ var r = YAHOO.util.Dom.getRegion(c);
+ YAHOO.util.Dom.setStyle(this.maskEl, "width", r.right - r.left + "px");
+ YAHOO.util.Dom.setStyle(this.maskEl, "height", r.bottom - r.top + "px");
+ }
+ },
+
+ /**
+ * Renders the contents of the navigator
+ *
+ * @method renderNavContents
+ *
+ * @param {Array} html The HTML buffer to append the HTML to.
+ * @return {Array} A reference to the buffer passed in.
+ */
+ renderNavContents : function(html) {
+ var NAV = YAHOO.widget.CalendarNavigator,
+ C = NAV.CLASSES,
+ h = html; // just to use a shorter name
+
+ h[h.length] = '
';
+ this.renderMonth(h);
+ h[h.length] = '
';
+ h[h.length] = '
';
+ this.renderYear(h);
+ h[h.length] = '
';
+ h[h.length] = '
';
+ this.renderButtons(h);
+ h[h.length] = '
';
+ h[h.length] = '';
+
+ return h;
+ },
+
+ /**
+ * Renders the month label and control for the navigator
+ *
+ * @method renderNavContents
+ * @param {Array} html The HTML buffer to append the HTML to.
+ * @return {Array} A reference to the buffer passed in.
+ */
+ renderMonth : function(html) {
+ var NAV = YAHOO.widget.CalendarNavigator,
+ C = NAV.CLASSES;
+
+ var id = this.id + NAV.MONTH_SUFFIX,
+ mf = this.__getCfg("monthFormat"),
+ months = this.cal.cfg.getProperty((mf == YAHOO.widget.Calendar.SHORT) ? "MONTHS_SHORT" : "MONTHS_LONG"),
+ h = html;
+
+ if (months && months.length > 0) {
+ h[h.length] = '';
+ h[h.length] = '';
+ }
+ return h;
+ },
+
+ /**
+ * Renders the year label and control for the navigator
+ *
+ * @method renderYear
+ * @param {Array} html The HTML buffer to append the HTML to.
+ * @return {Array} A reference to the buffer passed in.
+ */
+ renderYear : function(html) {
+ var NAV = YAHOO.widget.CalendarNavigator,
+ C = NAV.CLASSES;
+
+ var id = this.id + NAV.YEAR_SUFFIX,
+ size = NAV.YR_MAX_DIGITS,
+ h = html;
+
+ h[h.length] = '';
+ h[h.length] = '';
+ return h;
+ },
+
+ /**
+ * Renders the submit/cancel buttons for the navigator
+ *
+ * @method renderButton
+ * @return {String} The HTML created for the Button UI
+ */
+ renderButtons : function(html) {
+ var C = YAHOO.widget.CalendarNavigator.CLASSES;
+ var h = html;
+
+ h[h.length] = '';
+ h[h.length] = '';
+ h[h.length] = '';
+ h[h.length] = '';
+ h[h.length] = '';
+ h[h.length] = '';
+
+ return h;
+ },
+
+ /**
+ * Attaches DOM event listeners to the rendered elements
+ *
+ * The method will call applyKeyListeners, to setup keyboard specific
+ * listeners
+ *
+ * @method applyListeners
+ */
+ applyListeners : function() {
+ var E = YAHOO.util.Event;
+
+ function yearUpdateHandler() {
+ if (this.validate()) {
+ this.setYear(this._getYearFromUI());
+ }
+ }
+
+ function monthUpdateHandler() {
+ this.setMonth(this._getMonthFromUI());
+ }
+
+ E.on(this.submitEl, "click", this.submit, this, true);
+ E.on(this.cancelEl, "click", this.cancel, this, true);
+ E.on(this.yearEl, "blur", yearUpdateHandler, this, true);
+ E.on(this.monthEl, "change", monthUpdateHandler, this, true);
+
+ if (this.__isIEQuirks) {
+ YAHOO.util.Event.on(this.cal.oDomContainer, "resize", this._syncMask, this, true);
+ }
+
+ this.applyKeyListeners();
+ },
+
+ /**
+ * Removes/purges DOM event listeners from the rendered elements
+ *
+ * @method purgeListeners
+ */
+ purgeListeners : function() {
+ var E = YAHOO.util.Event;
+ E.removeListener(this.submitEl, "click", this.submit);
+ E.removeListener(this.cancelEl, "click", this.cancel);
+ E.removeListener(this.yearEl, "blur");
+ E.removeListener(this.monthEl, "change");
+ if (this.__isIEQuirks) {
+ E.removeListener(this.cal.oDomContainer, "resize", this._syncMask);
+ }
+
+ this.purgeKeyListeners();
+ },
+
+ /**
+ * Attaches DOM listeners for keyboard support.
+ * Tab/Shift-Tab looping, Enter Key Submit on Year element,
+ * Up/Down/PgUp/PgDown year increment on Year element
+ *
+ * NOTE: MacOSX Safari 2.x doesn't let you tab to buttons and
+ * MacOSX Gecko does not let you tab to buttons or select controls,
+ * so for these browsers, Tab/Shift-Tab looping is limited to the
+ * elements which can be reached using the tab key.
+ *
+ * @method applyKeyListeners
+ */
+ applyKeyListeners : function() {
+ var E = YAHOO.util.Event,
+ ua = YAHOO.env.ua;
+
+ // IE/Safari 3.1 doesn't fire keypress for arrow/pg keys (non-char keys)
+ var arrowEvt = (ua.ie || ua.webkit) ? "keydown" : "keypress";
+
+ // - IE/Safari 3.1 doesn't fire keypress for non-char keys
+ // - Opera doesn't allow us to cancel keydown or keypress for tab, but
+ // changes focus successfully on keydown (keypress is too late to change focus - opera's already moved on).
+ var tabEvt = (ua.ie || ua.opera || ua.webkit) ? "keydown" : "keypress";
+
+ // Everyone likes keypress for Enter (char keys) - whoo hoo!
+ E.on(this.yearEl, "keypress", this._handleEnterKey, this, true);
+
+ E.on(this.yearEl, arrowEvt, this._handleDirectionKeys, this, true);
+ E.on(this.lastCtrl, tabEvt, this._handleTabKey, this, true);
+ E.on(this.firstCtrl, tabEvt, this._handleShiftTabKey, this, true);
+ },
+
+ /**
+ * Removes/purges DOM listeners for keyboard support
+ *
+ * @method purgeKeyListeners
+ */
+ purgeKeyListeners : function() {
+ var E = YAHOO.util.Event,
+ ua = YAHOO.env.ua;
+
+ var arrowEvt = (ua.ie || ua.webkit) ? "keydown" : "keypress";
+ var tabEvt = (ua.ie || ua.opera || ua.webkit) ? "keydown" : "keypress";
+
+ E.removeListener(this.yearEl, "keypress", this._handleEnterKey);
+ E.removeListener(this.yearEl, arrowEvt, this._handleDirectionKeys);
+ E.removeListener(this.lastCtrl, tabEvt, this._handleTabKey);
+ E.removeListener(this.firstCtrl, tabEvt, this._handleShiftTabKey);
+ },
+
+ /**
+ * Updates the Calendar/CalendarGroup's pagedate with the currently set month and year if valid.
+ *
+ * If the currently set month/year is invalid, a validation error will be displayed and the
+ * Calendar/CalendarGroup's pagedate will not be updated.
+ *
+ * @method submit
+ */
+ submit : function() {
+ if (this.validate()) {
+ this.hide();
+
+ this.setMonth(this._getMonthFromUI());
+ this.setYear(this._getYearFromUI());
+
+ var cal = this.cal;
+ var nav = this;
+
+ function update() {
+ cal.setYear(nav.getYear());
+ cal.setMonth(nav.getMonth());
+ cal.render();
+ }
+ // Artificial delay, just to help the user see something changed
+ var delay = YAHOO.widget.CalendarNavigator.UPDATE_DELAY;
+ if (delay > 0) {
+ window.setTimeout(update, delay);
+ } else {
+ update();
+ }
+ }
+ },
+
+ /**
+ * Hides the navigator and mask, without updating the Calendar/CalendarGroup's state
+ *
+ * @method cancel
+ */
+ cancel : function() {
+ this.hide();
+ },
+
+ /**
+ * Validates the current state of the UI controls
+ *
+ * @method validate
+ * @return {Boolean} true, if the current UI state contains valid values, false if not
+ */
+ validate : function() {
+ if (this._getYearFromUI() !== null) {
+ this.clearErrors();
+ return true;
+ } else {
+ this.setYearError();
+ this.setError(this.__getCfg("invalidYear", true));
+ return false;
+ }
+ },
+
+ /**
+ * Displays an error message in the Navigator's error panel
+ * @method setError
+ * @param {String} msg The error message to display
+ */
+ setError : function(msg) {
+ if (this.errorEl) {
+ this.errorEl.innerHTML = msg;
+ this._show(this.errorEl, true);
+ }
+ },
+
+ /**
+ * Clears the navigator's error message and hides the error panel
+ * @method clearError
+ */
+ clearError : function() {
+ if (this.errorEl) {
+ this.errorEl.innerHTML = "";
+ this._show(this.errorEl, false);
+ }
+ },
+
+ /**
+ * Displays the validation error UI for the year control
+ * @method setYearError
+ */
+ setYearError : function() {
+ YAHOO.util.Dom.addClass(this.yearEl, YAHOO.widget.CalendarNavigator.CLASSES.INVALID);
+ },
+
+ /**
+ * Removes the validation error UI for the year control
+ * @method clearYearError
+ */
+ clearYearError : function() {
+ YAHOO.util.Dom.removeClass(this.yearEl, YAHOO.widget.CalendarNavigator.CLASSES.INVALID);
+ },
+
+ /**
+ * Clears all validation and error messages in the UI
+ * @method clearErrors
+ */
+ clearErrors : function() {
+ this.clearError();
+ this.clearYearError();
+ },
+
+ /**
+ * Sets the initial focus, based on the configured value
+ * @method setInitialFocus
+ */
+ setInitialFocus : function() {
+ var el = this.submitEl,
+ f = this.__getCfg("initialFocus");
+
+ if (f && f.toLowerCase) {
+ f = f.toLowerCase();
+ if (f == "year") {
+ el = this.yearEl;
+ try {
+ this.yearEl.select();
+ } catch (err) {
+ // Ignore;
+ }
+ } else if (f == "month") {
+ el = this.monthEl;
+ }
+ }
+
+ if (el && YAHOO.lang.isFunction(el.focus)) {
+ try {
+ el.focus();
+ } catch (err) {
+ // TODO: Fall back if focus fails?
+ }
+ }
+ },
+
+ /**
+ * Removes all renderered HTML elements for the Navigator from
+ * the DOM, purges event listeners and clears (nulls) any property
+ * references to HTML references
+ * @method erase
+ */
+ erase : function() {
+ if (this.__rendered) {
+ this.purgeListeners();
+
+ // Clear out innerHTML references
+ this.yearEl = null;
+ this.monthEl = null;
+ this.errorEl = null;
+ this.submitEl = null;
+ this.cancelEl = null;
+ this.firstCtrl = null;
+ this.lastCtrl = null;
+ if (this.navEl) {
+ this.navEl.innerHTML = "";
+ }
+
+ var p = this.navEl.parentNode;
+ if (p) {
+ p.removeChild(this.navEl);
+ }
+ this.navEl = null;
+
+ var pm = this.maskEl.parentNode;
+ if (pm) {
+ pm.removeChild(this.maskEl);
+ }
+ this.maskEl = null;
+ this.__rendered = false;
+ }
+ },
+
+ /**
+ * Destroys the Navigator object and any HTML references
+ * @method destroy
+ */
+ destroy : function() {
+ this.erase();
+ this._doc = null;
+ this.cal = null;
+ this.id = null;
+ },
+
+ /**
+ * Protected implementation to handle how UI elements are
+ * hidden/shown.
+ *
+ * @method _show
+ * @protected
+ */
+ _show : function(el, bShow) {
+ if (el) {
+ YAHOO.util.Dom.setStyle(el, "display", (bShow) ? "block" : "none");
+ }
+ },
+
+ /**
+ * Returns the month value (index), from the month UI element
+ * @protected
+ * @method _getMonthFromUI
+ * @return {Number} The month index, or 0 if a UI element for the month
+ * is not found
+ */
+ _getMonthFromUI : function() {
+ if (this.monthEl) {
+ return this.monthEl.selectedIndex;
+ } else {
+ return 0; // Default to Jan
+ }
+ },
+
+ /**
+ * Returns the year value, from the Navitator's year UI element
+ * @protected
+ * @method _getYearFromUI
+ * @return {Number} The year value set in the UI, if valid. null is returned if
+ * the UI does not contain a valid year value.
+ */
+ _getYearFromUI : function() {
+ var NAV = YAHOO.widget.CalendarNavigator;
+
+ var yr = null;
+ if (this.yearEl) {
+ var value = this.yearEl.value;
+ value = value.replace(NAV.TRIM, "$1");
+
+ if (NAV.YR_PATTERN.test(value)) {
+ yr = parseInt(value, 10);
+ }
+ }
+ return yr;
+ },
+
+ /**
+ * Updates the Navigator's year UI, based on the year value set on the Navigator object
+ * @protected
+ * @method _updateYearUI
+ */
+ _updateYearUI : function() {
+ if (this.yearEl && this._year !== null) {
+ this.yearEl.value = this._year;
+ }
+ },
+
+ /**
+ * Updates the Navigator's month UI, based on the month value set on the Navigator object
+ * @protected
+ * @method _updateMonthUI
+ */
+ _updateMonthUI : function() {
+ if (this.monthEl) {
+ this.monthEl.selectedIndex = this._month;
+ }
+ },
+
+ /**
+ * Sets up references to the first and last focusable element in the Navigator's UI
+ * in terms of tab order (Naviagator's firstEl and lastEl properties). The references
+ * are used to control modality by looping around from the first to the last control
+ * and visa versa for tab/shift-tab navigation.
+ *
+ * @protected
+ * @method _setFirstLastElements
+ */
+ _setFirstLastElements : function() {
+ this.firstCtrl = this.monthEl;
+ this.lastCtrl = this.cancelEl;
+
+ // Special handling for MacOSX.
+ // - Safari 2.x can't focus on buttons
+ // - Gecko can't focus on select boxes or buttons
+ if (this.__isMac) {
+ if (YAHOO.env.ua.webkit && YAHOO.env.ua.webkit < 420){
+ this.firstCtrl = this.monthEl;
+ this.lastCtrl = this.yearEl;
+ }
+ if (YAHOO.env.ua.gecko) {
+ this.firstCtrl = this.yearEl;
+ this.lastCtrl = this.yearEl;
+ }
+ }
+ },
+
+ /**
+ * Default Keyboard event handler to capture Enter
+ * on the Navigator's year control (yearEl)
+ *
+ * @method _handleEnterKey
+ * @protected
+ * @param {Event} e The DOM event being handled
+ */
+ _handleEnterKey : function(e) {
+ var KEYS = YAHOO.util.KeyListener.KEY;
+
+ if (YAHOO.util.Event.getCharCode(e) == KEYS.ENTER) {
+ YAHOO.util.Event.preventDefault(e);
+ this.submit();
+ }
+ },
+
+ /**
+ * Default Keyboard event handler to capture up/down/pgup/pgdown
+ * on the Navigator's year control (yearEl).
+ *
+ * @method _handleDirectionKeys
+ * @protected
+ * @param {Event} e The DOM event being handled
+ */
+ _handleDirectionKeys : function(e) {
+ var E = YAHOO.util.Event,
+ KEYS = YAHOO.util.KeyListener.KEY,
+ NAV = YAHOO.widget.CalendarNavigator;
+
+ var value = (this.yearEl.value) ? parseInt(this.yearEl.value, 10) : null;
+ if (isFinite(value)) {
+ var dir = false;
+ switch(E.getCharCode(e)) {
+ case KEYS.UP:
+ this.yearEl.value = value + NAV.YR_MINOR_INC;
+ dir = true;
+ break;
+ case KEYS.DOWN:
+ this.yearEl.value = Math.max(value - NAV.YR_MINOR_INC, 0);
+ dir = true;
+ break;
+ case KEYS.PAGE_UP:
+ this.yearEl.value = value + NAV.YR_MAJOR_INC;
+ dir = true;
+ break;
+ case KEYS.PAGE_DOWN:
+ this.yearEl.value = Math.max(value - NAV.YR_MAJOR_INC, 0);
+ dir = true;
+ break;
+ default:
+ break;
+ }
+ if (dir) {
+ E.preventDefault(e);
+ try {
+ this.yearEl.select();
+ } catch(err) {
+ // Ignore
+ }
+ }
+ }
+ },
+
+ /**
+ * Default Keyboard event handler to capture Tab
+ * on the last control (lastCtrl) in the Navigator.
+ *
+ * @method _handleTabKey
+ * @protected
+ * @param {Event} e The DOM event being handled
+ */
+ _handleTabKey : function(e) {
+ var E = YAHOO.util.Event,
+ KEYS = YAHOO.util.KeyListener.KEY;
+
+ if (E.getCharCode(e) == KEYS.TAB && !e.shiftKey) {
+ try {
+ E.preventDefault(e);
+ this.firstCtrl.focus();
+ } catch (err) {
+ // Ignore - mainly for focus edge cases
+ }
+ }
+ },
+
+ /**
+ * Default Keyboard event handler to capture Shift-Tab
+ * on the first control (firstCtrl) in the Navigator.
+ *
+ * @method _handleShiftTabKey
+ * @protected
+ * @param {Event} e The DOM event being handled
+ */
+ _handleShiftTabKey : function(e) {
+ var E = YAHOO.util.Event,
+ KEYS = YAHOO.util.KeyListener.KEY;
+
+ if (e.shiftKey && E.getCharCode(e) == KEYS.TAB) {
+ try {
+ E.preventDefault(e);
+ this.lastCtrl.focus();
+ } catch (err) {
+ // Ignore - mainly for focus edge cases
+ }
+ }
+ },
+
+ /**
+ * Retrieve Navigator configuration values from
+ * the parent Calendar/CalendarGroup's config value.
+ *
+ * If it has not been set in the user provided configuration, the method will
+ * return the default value of the configuration property, as set in _DEFAULT_CFG
+ *
";B[B.length]='';return B;},renderMonth:function(D){var G=YAHOO.widget.CalendarNavigator,H=G.CLASSES;var I=this.id+G.MONTH_SUFFIX,F=this.__getCfg("monthFormat"),A=this.cal.cfg.getProperty((F==YAHOO.widget.Calendar.SHORT)?"MONTHS_SHORT":"MONTHS_LONG"),E=D;if(A&&A.length>0){E[E.length]='";E[E.length]='";}return E;},renderYear:function(B){var E=YAHOO.widget.CalendarNavigator,F=E.CLASSES;var G=this.id+E.YEAR_SUFFIX,A=E.YR_MAX_DIGITS,D=B;D[D.length]='";D[D.length]='';return D;},renderButtons:function(A){var D=YAHOO.widget.CalendarNavigator.CLASSES;var B=A;B[B.length]='';B[B.length]='";B[B.length]="";B[B.length]='';B[B.length]='";B[B.length]="";return B;},applyListeners:function(){var B=YAHOO.util.Event;function A(){if(this.validate()){this.setYear(this._getYearFromUI());}}function C(){this.setMonth(this._getMonthFromUI());}B.on(this.submitEl,"click",this.submit,this,true);B.on(this.cancelEl,"click",this.cancel,this,true);B.on(this.yearEl,"blur",A,this,true);B.on(this.monthEl,"change",C,this,true);if(this.__isIEQuirks){YAHOO.util.Event.on(this.cal.oDomContainer,"resize",this._syncMask,this,true);}this.applyKeyListeners();},purgeListeners:function(){var A=YAHOO.util.Event;A.removeListener(this.submitEl,"click",this.submit);A.removeListener(this.cancelEl,"click",this.cancel);A.removeListener(this.yearEl,"blur");A.removeListener(this.monthEl,"change");if(this.__isIEQuirks){A.removeListener(this.cal.oDomContainer,"resize",this._syncMask);}this.purgeKeyListeners();},applyKeyListeners:function(){var D=YAHOO.util.Event,A=YAHOO.env.ua;var C=(A.ie||A.webkit)?"keydown":"keypress";var B=(A.ie||A.opera||A.webkit)?"keydown":"keypress";D.on(this.yearEl,"keypress",this._handleEnterKey,this,true);D.on(this.yearEl,C,this._handleDirectionKeys,this,true);D.on(this.lastCtrl,B,this._handleTabKey,this,true);D.on(this.firstCtrl,B,this._handleShiftTabKey,this,true);},purgeKeyListeners:function(){var D=YAHOO.util.Event,A=YAHOO.env.ua;var C=(A.ie||A.webkit)?"keydown":"keypress";var B=(A.ie||A.opera||A.webkit)?"keydown":"keypress";D.removeListener(this.yearEl,"keypress",this._handleEnterKey);D.removeListener(this.yearEl,C,this._handleDirectionKeys);D.removeListener(this.lastCtrl,B,this._handleTabKey);D.removeListener(this.firstCtrl,B,this._handleShiftTabKey);},submit:function(){if(this.validate()){this.hide();this.setMonth(this._getMonthFromUI());this.setYear(this._getYearFromUI());
+var B=this.cal;var C=this;function D(){B.setYear(C.getYear());B.setMonth(C.getMonth());B.render();}var A=YAHOO.widget.CalendarNavigator.UPDATE_DELAY;if(A>0){window.setTimeout(D,A);}else{D();}}},cancel:function(){this.hide();},validate:function(){if(this._getYearFromUI()!==null){this.clearErrors();return true;}else{this.setYearError();this.setError(this.__getCfg("invalidYear",true));return false;}},setError:function(A){if(this.errorEl){this.errorEl.innerHTML=A;this._show(this.errorEl,true);}},clearError:function(){if(this.errorEl){this.errorEl.innerHTML="";this._show(this.errorEl,false);}},setYearError:function(){YAHOO.util.Dom.addClass(this.yearEl,YAHOO.widget.CalendarNavigator.CLASSES.INVALID);},clearYearError:function(){YAHOO.util.Dom.removeClass(this.yearEl,YAHOO.widget.CalendarNavigator.CLASSES.INVALID);},clearErrors:function(){this.clearError();this.clearYearError();},setInitialFocus:function(){var A=this.submitEl,C=this.__getCfg("initialFocus");if(C&&C.toLowerCase){C=C.toLowerCase();if(C=="year"){A=this.yearEl;try{this.yearEl.select();}catch(B){}}else{if(C=="month"){A=this.monthEl;}}}if(A&&YAHOO.lang.isFunction(A.focus)){try{A.focus();}catch(B){}}},erase:function(){if(this.__rendered){this.purgeListeners();this.yearEl=null;this.monthEl=null;this.errorEl=null;this.submitEl=null;this.cancelEl=null;this.firstCtrl=null;this.lastCtrl=null;if(this.navEl){this.navEl.innerHTML="";}var B=this.navEl.parentNode;if(B){B.removeChild(this.navEl);}this.navEl=null;var A=this.maskEl.parentNode;if(A){A.removeChild(this.maskEl);}this.maskEl=null;this.__rendered=false;}},destroy:function(){this.erase();this._doc=null;this.cal=null;this.id=null;},_show:function(B,A){if(B){YAHOO.util.Dom.setStyle(B,"display",(A)?"block":"none");}},_getMonthFromUI:function(){if(this.monthEl){return this.monthEl.selectedIndex;}else{return 0;}},_getYearFromUI:function(){var B=YAHOO.widget.CalendarNavigator;var A=null;if(this.yearEl){var C=this.yearEl.value;C=C.replace(B.TRIM,"$1");if(B.YR_PATTERN.test(C)){A=parseInt(C,10);}}return A;},_updateYearUI:function(){if(this.yearEl&&this._year!==null){this.yearEl.value=this._year;}},_updateMonthUI:function(){if(this.monthEl){this.monthEl.selectedIndex=this._month;}},_setFirstLastElements:function(){this.firstCtrl=this.monthEl;this.lastCtrl=this.cancelEl;if(this.__isMac){if(YAHOO.env.ua.webkit&&YAHOO.env.ua.webkit<420){this.firstCtrl=this.monthEl;this.lastCtrl=this.yearEl;}if(YAHOO.env.ua.gecko){this.firstCtrl=this.yearEl;this.lastCtrl=this.yearEl;}}},_handleEnterKey:function(B){var A=YAHOO.util.KeyListener.KEY;if(YAHOO.util.Event.getCharCode(B)==A.ENTER){YAHOO.util.Event.preventDefault(B);this.submit();}},_handleDirectionKeys:function(H){var G=YAHOO.util.Event,A=YAHOO.util.KeyListener.KEY,D=YAHOO.widget.CalendarNavigator;var F=(this.yearEl.value)?parseInt(this.yearEl.value,10):null;if(isFinite(F)){var B=false;switch(G.getCharCode(H)){case A.UP:this.yearEl.value=F+D.YR_MINOR_INC;B=true;break;case A.DOWN:this.yearEl.value=Math.max(F-D.YR_MINOR_INC,0);B=true;break;case A.PAGE_UP:this.yearEl.value=F+D.YR_MAJOR_INC;B=true;break;case A.PAGE_DOWN:this.yearEl.value=Math.max(F-D.YR_MAJOR_INC,0);B=true;break;default:break;}if(B){G.preventDefault(H);try{this.yearEl.select();}catch(C){}}}},_handleTabKey:function(D){var C=YAHOO.util.Event,A=YAHOO.util.KeyListener.KEY;if(C.getCharCode(D)==A.TAB&&!D.shiftKey){try{C.preventDefault(D);this.firstCtrl.focus();}catch(B){}}},_handleShiftTabKey:function(D){var C=YAHOO.util.Event,A=YAHOO.util.KeyListener.KEY;if(D.shiftKey&&C.getCharCode(D)==A.TAB){try{C.preventDefault(D);this.lastCtrl.focus();}catch(B){}}},__getCfg:function(D,B){var C=YAHOO.widget.CalendarNavigator._DEFAULT_CFG;var A=this.cal.cfg.getProperty("navigator");if(B){return(A!==true&&A.strings&&A.strings[D])?A.strings[D]:C.strings[D];}else{return(A!==true&&A[D])?A[D]:C[D];}},__isMac:(navigator.userAgent.toLowerCase().indexOf("macintosh")!=-1)};YAHOO.register("calendar",YAHOO.widget.Calendar,{version:"2.5.2",build:"1076"});
\ No newline at end of file
diff --git a/lib/yui/calendar/calendar.js b/lib/yui/calendar/calendar.js
new file mode 100755
index 0000000000..a1911545e1
--- /dev/null
+++ b/lib/yui/calendar/calendar.js
@@ -0,0 +1,6851 @@
+/*
+Copyright (c) 2008, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 2.5.2
+*/
+(function () {
+
+ /**
+ * Config is a utility used within an Object to allow the implementer to
+ * maintain a list of local configuration properties and listen for changes
+ * to those properties dynamically using CustomEvent. The initial values are
+ * also maintained so that the configuration can be reset at any given point
+ * to its initial state.
+ * @namespace YAHOO.util
+ * @class Config
+ * @constructor
+ * @param {Object} owner The owner Object to which this Config Object belongs
+ */
+ YAHOO.util.Config = function (owner) {
+
+ if (owner) {
+ this.init(owner);
+ }
+
+
+ };
+
+
+ var Lang = YAHOO.lang,
+ CustomEvent = YAHOO.util.CustomEvent,
+ Config = YAHOO.util.Config;
+
+
+ /**
+ * Constant representing the CustomEvent type for the config changed event.
+ * @property YAHOO.util.Config.CONFIG_CHANGED_EVENT
+ * @private
+ * @static
+ * @final
+ */
+ Config.CONFIG_CHANGED_EVENT = "configChanged";
+
+ /**
+ * Constant representing the boolean type string
+ * @property YAHOO.util.Config.BOOLEAN_TYPE
+ * @private
+ * @static
+ * @final
+ */
+ Config.BOOLEAN_TYPE = "boolean";
+
+ Config.prototype = {
+
+ /**
+ * Object reference to the owner of this Config Object
+ * @property owner
+ * @type Object
+ */
+ owner: null,
+
+ /**
+ * Boolean flag that specifies whether a queue is currently
+ * being executed
+ * @property queueInProgress
+ * @type Boolean
+ */
+ queueInProgress: false,
+
+ /**
+ * Maintains the local collection of configuration property objects and
+ * their specified values
+ * @property config
+ * @private
+ * @type Object
+ */
+ config: null,
+
+ /**
+ * Maintains the local collection of configuration property objects as
+ * they were initially applied.
+ * This object is used when resetting a property.
+ * @property initialConfig
+ * @private
+ * @type Object
+ */
+ initialConfig: null,
+
+ /**
+ * Maintains the local, normalized CustomEvent queue
+ * @property eventQueue
+ * @private
+ * @type Object
+ */
+ eventQueue: null,
+
+ /**
+ * Custom Event, notifying subscribers when Config properties are set
+ * (setProperty is called without the silent flag
+ * @event configChangedEvent
+ */
+ configChangedEvent: null,
+
+ /**
+ * Initializes the configuration Object and all of its local members.
+ * @method init
+ * @param {Object} owner The owner Object to which this Config
+ * Object belongs
+ */
+ init: function (owner) {
+
+ this.owner = owner;
+
+ this.configChangedEvent =
+ this.createEvent(Config.CONFIG_CHANGED_EVENT);
+
+ this.configChangedEvent.signature = CustomEvent.LIST;
+ this.queueInProgress = false;
+ this.config = {};
+ this.initialConfig = {};
+ this.eventQueue = [];
+
+ },
+
+ /**
+ * Validates that the value passed in is a Boolean.
+ * @method checkBoolean
+ * @param {Object} val The value to validate
+ * @return {Boolean} true, if the value is valid
+ */
+ checkBoolean: function (val) {
+ return (typeof val == Config.BOOLEAN_TYPE);
+ },
+
+ /**
+ * Validates that the value passed in is a number.
+ * @method checkNumber
+ * @param {Object} val The value to validate
+ * @return {Boolean} true, if the value is valid
+ */
+ checkNumber: function (val) {
+ return (!isNaN(val));
+ },
+
+ /**
+ * Fires a configuration property event using the specified value.
+ * @method fireEvent
+ * @private
+ * @param {String} key The configuration property's name
+ * @param {value} Object The value of the correct type for the property
+ */
+ fireEvent: function ( key, value ) {
+ var property = this.config[key];
+
+ if (property && property.event) {
+ property.event.fire(value);
+ }
+ },
+
+ /**
+ * Adds a property to the Config Object's private config hash.
+ * @method addProperty
+ * @param {String} key The configuration property's name
+ * @param {Object} propertyObject The Object containing all of this
+ * property's arguments
+ */
+ addProperty: function ( key, propertyObject ) {
+ key = key.toLowerCase();
+
+ this.config[key] = propertyObject;
+
+ propertyObject.event = this.createEvent(key, { scope: this.owner });
+ propertyObject.event.signature = CustomEvent.LIST;
+
+
+ propertyObject.key = key;
+
+ if (propertyObject.handler) {
+ propertyObject.event.subscribe(propertyObject.handler,
+ this.owner);
+ }
+
+ this.setProperty(key, propertyObject.value, true);
+
+ if (! propertyObject.suppressEvent) {
+ this.queueProperty(key, propertyObject.value);
+ }
+
+ },
+
+ /**
+ * Returns a key-value configuration map of the values currently set in
+ * the Config Object.
+ * @method getConfig
+ * @return {Object} The current config, represented in a key-value map
+ */
+ getConfig: function () {
+
+ var cfg = {},
+ prop,
+ property;
+
+ for (prop in this.config) {
+ property = this.config[prop];
+ if (property && property.event) {
+ cfg[prop] = property.value;
+ }
+ }
+
+ return cfg;
+ },
+
+ /**
+ * Returns the value of specified property.
+ * @method getProperty
+ * @param {String} key The name of the property
+ * @return {Object} The value of the specified property
+ */
+ getProperty: function (key) {
+ var property = this.config[key.toLowerCase()];
+ if (property && property.event) {
+ return property.value;
+ } else {
+ return undefined;
+ }
+ },
+
+ /**
+ * Resets the specified property's value to its initial value.
+ * @method resetProperty
+ * @param {String} key The name of the property
+ * @return {Boolean} True is the property was reset, false if not
+ */
+ resetProperty: function (key) {
+
+ key = key.toLowerCase();
+
+ var property = this.config[key];
+
+ if (property && property.event) {
+
+ if (this.initialConfig[key] &&
+ !Lang.isUndefined(this.initialConfig[key])) {
+
+ this.setProperty(key, this.initialConfig[key]);
+
+ return true;
+
+ }
+
+ } else {
+
+ return false;
+ }
+
+ },
+
+ /**
+ * Sets the value of a property. If the silent property is passed as
+ * true, the property's event will not be fired.
+ * @method setProperty
+ * @param {String} key The name of the property
+ * @param {String} value The value to set the property to
+ * @param {Boolean} silent Whether the value should be set silently,
+ * without firing the property event.
+ * @return {Boolean} True, if the set was successful, false if it failed.
+ */
+ setProperty: function (key, value, silent) {
+
+ var property;
+
+ key = key.toLowerCase();
+
+ if (this.queueInProgress && ! silent) {
+ // Currently running through a queue...
+ this.queueProperty(key,value);
+ return true;
+
+ } else {
+ property = this.config[key];
+ if (property && property.event) {
+ if (property.validator && !property.validator(value)) {
+ return false;
+ } else {
+ property.value = value;
+ if (! silent) {
+ this.fireEvent(key, value);
+ this.configChangedEvent.fire([key, value]);
+ }
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+ },
+
+ /**
+ * Sets the value of a property and queues its event to execute. If the
+ * event is already scheduled to execute, it is
+ * moved from its current position to the end of the queue.
+ * @method queueProperty
+ * @param {String} key The name of the property
+ * @param {String} value The value to set the property to
+ * @return {Boolean} true, if the set was successful, false if
+ * it failed.
+ */
+ queueProperty: function (key, value) {
+
+ key = key.toLowerCase();
+
+ var property = this.config[key],
+ foundDuplicate = false,
+ iLen,
+ queueItem,
+ queueItemKey,
+ queueItemValue,
+ sLen,
+ supercedesCheck,
+ qLen,
+ queueItemCheck,
+ queueItemCheckKey,
+ queueItemCheckValue,
+ i,
+ s,
+ q;
+
+ if (property && property.event) {
+
+ if (!Lang.isUndefined(value) && property.validator &&
+ !property.validator(value)) { // validator
+ return false;
+ } else {
+
+ if (!Lang.isUndefined(value)) {
+ property.value = value;
+ } else {
+ value = property.value;
+ }
+
+ foundDuplicate = false;
+ iLen = this.eventQueue.length;
+
+ for (i = 0; i < iLen; i++) {
+ queueItem = this.eventQueue[i];
+
+ if (queueItem) {
+ queueItemKey = queueItem[0];
+ queueItemValue = queueItem[1];
+
+ if (queueItemKey == key) {
+
+ /*
+ found a dupe... push to end of queue, null
+ current item, and break
+ */
+
+ this.eventQueue[i] = null;
+
+ this.eventQueue.push(
+ [key, (!Lang.isUndefined(value) ?
+ value : queueItemValue)]);
+
+ foundDuplicate = true;
+ break;
+ }
+ }
+ }
+
+ // this is a refire, or a new property in the queue
+
+ if (! foundDuplicate && !Lang.isUndefined(value)) {
+ this.eventQueue.push([key, value]);
+ }
+ }
+
+ if (property.supercedes) {
+
+ sLen = property.supercedes.length;
+
+ for (s = 0; s < sLen; s++) {
+
+ supercedesCheck = property.supercedes[s];
+ qLen = this.eventQueue.length;
+
+ for (q = 0; q < qLen; q++) {
+ queueItemCheck = this.eventQueue[q];
+
+ if (queueItemCheck) {
+ queueItemCheckKey = queueItemCheck[0];
+ queueItemCheckValue = queueItemCheck[1];
+
+ if (queueItemCheckKey ==
+ supercedesCheck.toLowerCase() ) {
+
+ this.eventQueue.push([queueItemCheckKey,
+ queueItemCheckValue]);
+
+ this.eventQueue[q] = null;
+ break;
+
+ }
+ }
+ }
+ }
+ }
+
+
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Fires the event for a property using the property's current value.
+ * @method refireEvent
+ * @param {String} key The name of the property
+ */
+ refireEvent: function (key) {
+
+ key = key.toLowerCase();
+
+ var property = this.config[key];
+
+ if (property && property.event &&
+
+ !Lang.isUndefined(property.value)) {
+
+ if (this.queueInProgress) {
+
+ this.queueProperty(key);
+
+ } else {
+
+ this.fireEvent(key, property.value);
+
+ }
+
+ }
+ },
+
+ /**
+ * Applies a key-value Object literal to the configuration, replacing
+ * any existing values, and queueing the property events.
+ * Although the values will be set, fireQueue() must be called for their
+ * associated events to execute.
+ * @method applyConfig
+ * @param {Object} userConfig The configuration Object literal
+ * @param {Boolean} init When set to true, the initialConfig will
+ * be set to the userConfig passed in, so that calling a reset will
+ * reset the properties to the passed values.
+ */
+ applyConfig: function (userConfig, init) {
+
+ var sKey,
+ oConfig;
+
+ if (init) {
+ oConfig = {};
+ for (sKey in userConfig) {
+ if (Lang.hasOwnProperty(userConfig, sKey)) {
+ oConfig[sKey.toLowerCase()] = userConfig[sKey];
+ }
+ }
+ this.initialConfig = oConfig;
+ }
+
+ for (sKey in userConfig) {
+ if (Lang.hasOwnProperty(userConfig, sKey)) {
+ this.queueProperty(sKey, userConfig[sKey]);
+ }
+ }
+ },
+
+ /**
+ * Refires the events for all configuration properties using their
+ * current values.
+ * @method refresh
+ */
+ refresh: function () {
+
+ var prop;
+
+ for (prop in this.config) {
+ this.refireEvent(prop);
+ }
+ },
+
+ /**
+ * Fires the normalized list of queued property change events
+ * @method fireQueue
+ */
+ fireQueue: function () {
+
+ var i,
+ queueItem,
+ key,
+ value,
+ property;
+
+ this.queueInProgress = true;
+ for (i = 0;i < this.eventQueue.length; i++) {
+ queueItem = this.eventQueue[i];
+ if (queueItem) {
+
+ key = queueItem[0];
+ value = queueItem[1];
+ property = this.config[key];
+
+ property.value = value;
+
+ this.fireEvent(key,value);
+ }
+ }
+
+ this.queueInProgress = false;
+ this.eventQueue = [];
+ },
+
+ /**
+ * Subscribes an external handler to the change event for any
+ * given property.
+ * @method subscribeToConfigEvent
+ * @param {String} key The property name
+ * @param {Function} handler The handler function to use subscribe to
+ * the property's event
+ * @param {Object} obj The Object to use for scoping the event handler
+ * (see CustomEvent documentation)
+ * @param {Boolean} override Optional. If true, will override "this"
+ * within the handler to map to the scope Object passed into the method.
+ * @return {Boolean} True, if the subscription was successful,
+ * otherwise false.
+ */
+ subscribeToConfigEvent: function (key, handler, obj, override) {
+
+ var property = this.config[key.toLowerCase()];
+
+ if (property && property.event) {
+ if (!Config.alreadySubscribed(property.event, handler, obj)) {
+ property.event.subscribe(handler, obj, override);
+ }
+ return true;
+ } else {
+ return false;
+ }
+
+ },
+
+ /**
+ * Unsubscribes an external handler from the change event for any
+ * given property.
+ * @method unsubscribeFromConfigEvent
+ * @param {String} key The property name
+ * @param {Function} handler The handler function to use subscribe to
+ * the property's event
+ * @param {Object} obj The Object to use for scoping the event
+ * handler (see CustomEvent documentation)
+ * @return {Boolean} True, if the unsubscription was successful,
+ * otherwise false.
+ */
+ unsubscribeFromConfigEvent: function (key, handler, obj) {
+ var property = this.config[key.toLowerCase()];
+ if (property && property.event) {
+ return property.event.unsubscribe(handler, obj);
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Returns a string representation of the Config object
+ * @method toString
+ * @return {String} The Config object in string format.
+ */
+ toString: function () {
+ var output = "Config";
+ if (this.owner) {
+ output += " [" + this.owner.toString() + "]";
+ }
+ return output;
+ },
+
+ /**
+ * Returns a string representation of the Config object's current
+ * CustomEvent queue
+ * @method outputEventQueue
+ * @return {String} The string list of CustomEvents currently queued
+ * for execution
+ */
+ outputEventQueue: function () {
+
+ var output = "",
+ queueItem,
+ q,
+ nQueue = this.eventQueue.length;
+
+ for (q = 0; q < nQueue; q++) {
+ queueItem = this.eventQueue[q];
+ if (queueItem) {
+ output += queueItem[0] + "=" + queueItem[1] + ", ";
+ }
+ }
+ return output;
+ },
+
+ /**
+ * Sets all properties to null, unsubscribes all listeners from each
+ * property's change event and all listeners from the configChangedEvent.
+ * @method destroy
+ */
+ destroy: function () {
+
+ var oConfig = this.config,
+ sProperty,
+ oProperty;
+
+
+ for (sProperty in oConfig) {
+
+ if (Lang.hasOwnProperty(oConfig, sProperty)) {
+
+ oProperty = oConfig[sProperty];
+
+ oProperty.event.unsubscribeAll();
+ oProperty.event = null;
+
+ }
+
+ }
+
+ this.configChangedEvent.unsubscribeAll();
+
+ this.configChangedEvent = null;
+ this.owner = null;
+ this.config = null;
+ this.initialConfig = null;
+ this.eventQueue = null;
+
+ }
+
+ };
+
+
+
+ /**
+ * Checks to determine if a particular function/Object pair are already
+ * subscribed to the specified CustomEvent
+ * @method YAHOO.util.Config.alreadySubscribed
+ * @static
+ * @param {YAHOO.util.CustomEvent} evt The CustomEvent for which to check
+ * the subscriptions
+ * @param {Function} fn The function to look for in the subscribers list
+ * @param {Object} obj The execution scope Object for the subscription
+ * @return {Boolean} true, if the function/Object pair is already subscribed
+ * to the CustomEvent passed in
+ */
+ Config.alreadySubscribed = function (evt, fn, obj) {
+
+ var nSubscribers = evt.subscribers.length,
+ subsc,
+ i;
+
+ if (nSubscribers > 0) {
+ i = nSubscribers - 1;
+ do {
+ subsc = evt.subscribers[i];
+ if (subsc && subsc.obj == obj && subsc.fn == fn) {
+ return true;
+ }
+ }
+ while (i--);
+ }
+
+ return false;
+
+ };
+
+ YAHOO.lang.augmentProto(Config, YAHOO.util.EventProvider);
+
+}());
+
+/**
+* YAHOO.widget.DateMath is used for simple date manipulation. The class is a static utility
+* used for adding, subtracting, and comparing dates.
+* @namespace YAHOO.widget
+* @class DateMath
+*/
+YAHOO.widget.DateMath = {
+ /**
+ * Constant field representing Day
+ * @property DAY
+ * @static
+ * @final
+ * @type String
+ */
+ DAY : "D",
+
+ /**
+ * Constant field representing Week
+ * @property WEEK
+ * @static
+ * @final
+ * @type String
+ */
+ WEEK : "W",
+
+ /**
+ * Constant field representing Year
+ * @property YEAR
+ * @static
+ * @final
+ * @type String
+ */
+ YEAR : "Y",
+
+ /**
+ * Constant field representing Month
+ * @property MONTH
+ * @static
+ * @final
+ * @type String
+ */
+ MONTH : "M",
+
+ /**
+ * Constant field representing one day, in milliseconds
+ * @property ONE_DAY_MS
+ * @static
+ * @final
+ * @type Number
+ */
+ ONE_DAY_MS : 1000*60*60*24,
+
+ /**
+ * Constant field representing the date in first week of January
+ * which identifies the first week of the year.
+ *
+ * In the U.S, Jan 1st is normally used based on a Sunday start of week.
+ * ISO 8601, used widely throughout Europe, uses Jan 4th, based on a Monday start of week.
+ *
+ * @property WEEK_ONE_JAN_DATE
+ * @static
+ * @type Number
+ */
+ WEEK_ONE_JAN_DATE : 1,
+
+ /**
+ * Adds the specified amount of time to the this instance.
+ * @method add
+ * @param {Date} date The JavaScript Date object to perform addition on
+ * @param {String} field The field constant to be used for performing addition.
+ * @param {Number} amount The number of units (measured in the field constant) to add to the date.
+ * @return {Date} The resulting Date object
+ */
+ add : function(date, field, amount) {
+ var d = new Date(date.getTime());
+ switch (field) {
+ case this.MONTH:
+ var newMonth = date.getMonth() + amount;
+ var years = 0;
+
+ if (newMonth < 0) {
+ while (newMonth < 0) {
+ newMonth += 12;
+ years -= 1;
+ }
+ } else if (newMonth > 11) {
+ while (newMonth > 11) {
+ newMonth -= 12;
+ years += 1;
+ }
+ }
+
+ d.setMonth(newMonth);
+ d.setFullYear(date.getFullYear() + years);
+ break;
+ case this.DAY:
+ this._addDays(d, amount);
+ // d.setDate(date.getDate() + amount);
+ break;
+ case this.YEAR:
+ d.setFullYear(date.getFullYear() + amount);
+ break;
+ case this.WEEK:
+ this._addDays(d, (amount * 7));
+ // d.setDate(date.getDate() + (amount * 7));
+ break;
+ }
+ return d;
+ },
+
+ /**
+ * Private helper method to account for bug in Safari 2 (webkit < 420)
+ * when Date.setDate(n) is called with n less than -128 or greater than 127.
+ *
+ * Fix approach and original findings are available here:
+ * http://brianary.blogspot.com/2006/03/safari-date-bug.html
+ *
+ * @method _addDays
+ * @param {Date} d JavaScript date object
+ * @param {Number} nDays The number of days to add to the date object (can be negative)
+ * @private
+ */
+ _addDays : function(d, nDays) {
+ if (YAHOO.env.ua.webkit && YAHOO.env.ua.webkit < 420) {
+ if (nDays < 0) {
+ // Ensure we don't go below -128 (getDate() is always 1 to 31, so we won't go above 127)
+ for(var min = -128; nDays < min; nDays -= min) {
+ d.setDate(d.getDate() + min);
+ }
+ } else {
+ // Ensure we don't go above 96 + 31 = 127
+ for(var max = 96; nDays > max; nDays -= max) {
+ d.setDate(d.getDate() + max);
+ }
+ }
+ // nDays should be remainder between -128 and 96
+ }
+ d.setDate(d.getDate() + nDays);
+ },
+
+ /**
+ * Subtracts the specified amount of time from the this instance.
+ * @method subtract
+ * @param {Date} date The JavaScript Date object to perform subtraction on
+ * @param {Number} field The this field constant to be used for performing subtraction.
+ * @param {Number} amount The number of units (measured in the field constant) to subtract from the date.
+ * @return {Date} The resulting Date object
+ */
+ subtract : function(date, field, amount) {
+ return this.add(date, field, (amount*-1));
+ },
+
+ /**
+ * Determines whether a given date is before another date on the calendar.
+ * @method before
+ * @param {Date} date The Date object to compare with the compare argument
+ * @param {Date} compareTo The Date object to use for the comparison
+ * @return {Boolean} true if the date occurs before the compared date; false if not.
+ */
+ before : function(date, compareTo) {
+ var ms = compareTo.getTime();
+ if (date.getTime() < ms) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Determines whether a given date is after another date on the calendar.
+ * @method after
+ * @param {Date} date The Date object to compare with the compare argument
+ * @param {Date} compareTo The Date object to use for the comparison
+ * @return {Boolean} true if the date occurs after the compared date; false if not.
+ */
+ after : function(date, compareTo) {
+ var ms = compareTo.getTime();
+ if (date.getTime() > ms) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Determines whether a given date is between two other dates on the calendar.
+ * @method between
+ * @param {Date} date The date to check for
+ * @param {Date} dateBegin The start of the range
+ * @param {Date} dateEnd The end of the range
+ * @return {Boolean} true if the date occurs between the compared dates; false if not.
+ */
+ between : function(date, dateBegin, dateEnd) {
+ if (this.after(date, dateBegin) && this.before(date, dateEnd)) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Retrieves a JavaScript Date object representing January 1 of any given year.
+ * @method getJan1
+ * @param {Number} calendarYear The calendar year for which to retrieve January 1
+ * @return {Date} January 1 of the calendar year specified.
+ */
+ getJan1 : function(calendarYear) {
+ return this.getDate(calendarYear,0,1);
+ },
+
+ /**
+ * Calculates the number of days the specified date is from January 1 of the specified calendar year.
+ * Passing January 1 to this function would return an offset value of zero.
+ * @method getDayOffset
+ * @param {Date} date The JavaScript date for which to find the offset
+ * @param {Number} calendarYear The calendar year to use for determining the offset
+ * @return {Number} The number of days since January 1 of the given year
+ */
+ getDayOffset : function(date, calendarYear) {
+ var beginYear = this.getJan1(calendarYear); // Find the start of the year. This will be in week 1.
+
+ // Find the number of days the passed in date is away from the calendar year start
+ var dayOffset = Math.ceil((date.getTime()-beginYear.getTime()) / this.ONE_DAY_MS);
+ return dayOffset;
+ },
+
+ /**
+ * Calculates the week number for the given date. Can currently support standard
+ * U.S. week numbers, based on Jan 1st defining the 1st week of the year, and
+ * ISO8601 week numbers, based on Jan 4th defining the 1st week of the year.
+ *
+ * @method getWeekNumber
+ * @param {Date} date The JavaScript date for which to find the week number
+ * @param {Number} firstDayOfWeek The index of the first day of the week (0 = Sun, 1 = Mon ... 6 = Sat).
+ * Defaults to 0
+ * @param {Number} janDate The date in the first week of January which defines week one for the year
+ * Defaults to the value of YAHOO.widget.DateMath.WEEK_ONE_JAN_DATE, which is 1 (Jan 1st).
+ * For the U.S, this is normally Jan 1st. ISO8601 uses Jan 4th to define the first week of the year.
+ *
+ * @return {Number} The number of the week containing the given date.
+ */
+ getWeekNumber : function(date, firstDayOfWeek, janDate) {
+
+ // Setup Defaults
+ firstDayOfWeek = firstDayOfWeek || 0;
+ janDate = janDate || this.WEEK_ONE_JAN_DATE;
+
+ var targetDate = this.clearTime(date),
+ startOfWeek,
+ endOfWeek;
+
+ if (targetDate.getDay() === firstDayOfWeek) {
+ startOfWeek = targetDate;
+ } else {
+ startOfWeek = this.getFirstDayOfWeek(targetDate, firstDayOfWeek);
+ }
+
+ var startYear = startOfWeek.getFullYear(),
+ startTime = startOfWeek.getTime();
+
+ // DST shouldn't be a problem here, math is quicker than setDate();
+ endOfWeek = new Date(startOfWeek.getTime() + 6*this.ONE_DAY_MS);
+
+ var weekNum;
+ if (startYear !== endOfWeek.getFullYear() && endOfWeek.getDate() >= janDate) {
+ // If years don't match, endOfWeek is in Jan. and if the
+ // week has WEEK_ONE_JAN_DATE in it, it's week one by definition.
+ weekNum = 1;
+ } else {
+ // Get the 1st day of the 1st week, and
+ // find how many days away we are from it.
+ var weekOne = this.clearTime(this.getDate(startYear, 0, janDate)),
+ weekOneDayOne = this.getFirstDayOfWeek(weekOne, firstDayOfWeek);
+
+ // Round days to smoothen out 1 hr DST diff
+ var daysDiff = Math.round((targetDate.getTime() - weekOneDayOne.getTime())/this.ONE_DAY_MS);
+
+ // Calc. Full Weeks
+ var rem = daysDiff % 7;
+ var weeksDiff = (daysDiff - rem)/7;
+ weekNum = weeksDiff + 1;
+ }
+ return weekNum;
+ },
+
+ /**
+ * Get the first day of the week, for the give date.
+ * @param {Date} dt The date in the week for which the first day is required.
+ * @param {Number} startOfWeek The index for the first day of the week, 0 = Sun, 1 = Mon ... 6 = Sat (defaults to 0)
+ * @return {Date} The first day of the week
+ */
+ getFirstDayOfWeek : function (dt, startOfWeek) {
+ startOfWeek = startOfWeek || 0;
+ var dayOfWeekIndex = dt.getDay(),
+ dayOfWeek = (dayOfWeekIndex - startOfWeek + 7) % 7;
+
+ return this.subtract(dt, this.DAY, dayOfWeek);
+ },
+
+ /**
+ * Determines if a given week overlaps two different years.
+ * @method isYearOverlapWeek
+ * @param {Date} weekBeginDate The JavaScript Date representing the first day of the week.
+ * @return {Boolean} true if the date overlaps two different years.
+ */
+ isYearOverlapWeek : function(weekBeginDate) {
+ var overlaps = false;
+ var nextWeek = this.add(weekBeginDate, this.DAY, 6);
+ if (nextWeek.getFullYear() != weekBeginDate.getFullYear()) {
+ overlaps = true;
+ }
+ return overlaps;
+ },
+
+ /**
+ * Determines if a given week overlaps two different months.
+ * @method isMonthOverlapWeek
+ * @param {Date} weekBeginDate The JavaScript Date representing the first day of the week.
+ * @return {Boolean} true if the date overlaps two different months.
+ */
+ isMonthOverlapWeek : function(weekBeginDate) {
+ var overlaps = false;
+ var nextWeek = this.add(weekBeginDate, this.DAY, 6);
+ if (nextWeek.getMonth() != weekBeginDate.getMonth()) {
+ overlaps = true;
+ }
+ return overlaps;
+ },
+
+ /**
+ * Gets the first day of a month containing a given date.
+ * @method findMonthStart
+ * @param {Date} date The JavaScript Date used to calculate the month start
+ * @return {Date} The JavaScript Date representing the first day of the month
+ */
+ findMonthStart : function(date) {
+ var start = this.getDate(date.getFullYear(), date.getMonth(), 1);
+ return start;
+ },
+
+ /**
+ * Gets the last day of a month containing a given date.
+ * @method findMonthEnd
+ * @param {Date} date The JavaScript Date used to calculate the month end
+ * @return {Date} The JavaScript Date representing the last day of the month
+ */
+ findMonthEnd : function(date) {
+ var start = this.findMonthStart(date);
+ var nextMonth = this.add(start, this.MONTH, 1);
+ var end = this.subtract(nextMonth, this.DAY, 1);
+ return end;
+ },
+
+ /**
+ * Clears the time fields from a given date, effectively setting the time to 12 noon.
+ * @method clearTime
+ * @param {Date} date The JavaScript Date for which the time fields will be cleared
+ * @return {Date} The JavaScript Date cleared of all time fields
+ */
+ clearTime : function(date) {
+ date.setHours(12,0,0,0);
+ return date;
+ },
+
+ /**
+ * Returns a new JavaScript Date object, representing the given year, month and date. Time fields (hr, min, sec, ms) on the new Date object
+ * are set to 0. The method allows Date instances to be created with the a year less than 100. "new Date(year, month, date)" implementations
+ * set the year to 19xx if a year (xx) which is less than 100 is provided.
+ *
+ * NOTE:Validation on argument values is not performed. It is the caller's responsibility to ensure
+ * arguments are valid as per the ECMAScript-262 Date object specification for the new Date(year, month[, date]) constructor.
+ *
+ * @method getDate
+ * @param {Number} y Year.
+ * @param {Number} m Month index from 0 (Jan) to 11 (Dec).
+ * @param {Number} d (optional) Date from 1 to 31. If not provided, defaults to 1.
+ * @return {Date} The JavaScript date object with year, month, date set as provided.
+ */
+ getDate : function(y, m, d) {
+ var dt = null;
+ if (YAHOO.lang.isUndefined(d)) {
+ d = 1;
+ }
+ if (y >= 100) {
+ dt = new Date(y, m, d);
+ } else {
+ dt = new Date();
+ dt.setFullYear(y);
+ dt.setMonth(m);
+ dt.setDate(d);
+ dt.setHours(0,0,0,0);
+ }
+ return dt;
+ }
+};
+
+/**
+* The Calendar component is a UI control that enables users to choose one or more dates from a graphical calendar presented in a one-month or
+* multi-month interface. Calendars are generated entirely via script and can be navigated without any page refreshes.
+* @module calendar
+* @title Calendar
+* @namespace YAHOO.widget
+* @requires yahoo,dom,event
+*/
+
+/**
+* Calendar is the base class for the Calendar widget. In its most basic
+* implementation, it has the ability to render a calendar widget on the page
+* that can be manipulated to select a single date, move back and forth between
+* months and years.
+*
To construct the placeholder for the calendar widget, the code is as
+* follows:
+*
+*
+*
+*
+*
+* NOTE: As of 2.4.0, the constructor's ID argument is optional.
+* The Calendar can be constructed by simply providing a container ID string,
+* or a reference to a container DIV HTMLElement (the element needs to exist
+* in the document).
+*
+* E.g.:
+*
+* var c = new YAHOO.widget.Calendar("calContainer", configOptions);
+*
+* or:
+*
+* var containerDiv = YAHOO.util.Dom.get("calContainer");
+* var c = new YAHOO.widget.Calendar(containerDiv, configOptions);
+*
+*
+*
+* If not provided, the ID will be generated from the container DIV ID by adding an "_t" suffix.
+* For example if an ID is not provided, and the container's ID is "calContainer", the Calendar's ID will be set to "calContainer_t".
+*
+*
+* @namespace YAHOO.widget
+* @class Calendar
+* @constructor
+* @param {String} id optional The id of the table element that will represent the Calendar widget. As of 2.4.0, this argument is optional.
+* @param {String | HTMLElement} container The id of the container div element that will wrap the Calendar table, or a reference to a DIV element which exists in the document.
+* @param {Object} config optional The configuration object containing the initial configuration values for the Calendar.
+*/
+YAHOO.widget.Calendar = function(id, containerId, config) {
+ this.init.apply(this, arguments);
+};
+
+/**
+* The path to be used for images loaded for the Calendar
+* @property YAHOO.widget.Calendar.IMG_ROOT
+* @static
+* @deprecated You can now customize images by overriding the calclose, calnavleft and calnavright default CSS classes for the close icon, left arrow and right arrow respectively
+* @type String
+*/
+YAHOO.widget.Calendar.IMG_ROOT = null;
+
+/**
+* Type constant used for renderers to represent an individual date (M/D/Y)
+* @property YAHOO.widget.Calendar.DATE
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.DATE = "D";
+
+/**
+* Type constant used for renderers to represent an individual date across any year (M/D)
+* @property YAHOO.widget.Calendar.MONTH_DAY
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.MONTH_DAY = "MD";
+
+/**
+* Type constant used for renderers to represent a weekday
+* @property YAHOO.widget.Calendar.WEEKDAY
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.WEEKDAY = "WD";
+
+/**
+* Type constant used for renderers to represent a range of individual dates (M/D/Y-M/D/Y)
+* @property YAHOO.widget.Calendar.RANGE
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.RANGE = "R";
+
+/**
+* Type constant used for renderers to represent a month across any year
+* @property YAHOO.widget.Calendar.MONTH
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.MONTH = "M";
+
+/**
+* Constant that represents the total number of date cells that are displayed in a given month
+* @property YAHOO.widget.Calendar.DISPLAY_DAYS
+* @static
+* @final
+* @type Number
+*/
+YAHOO.widget.Calendar.DISPLAY_DAYS = 42;
+
+/**
+* Constant used for halting the execution of the remainder of the render stack
+* @property YAHOO.widget.Calendar.STOP_RENDER
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.STOP_RENDER = "S";
+
+/**
+* Constant used to represent short date field string formats (e.g. Tu or Feb)
+* @property YAHOO.widget.Calendar.SHORT
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.SHORT = "short";
+
+/**
+* Constant used to represent long date field string formats (e.g. Monday or February)
+* @property YAHOO.widget.Calendar.LONG
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.LONG = "long";
+
+/**
+* Constant used to represent medium date field string formats (e.g. Mon)
+* @property YAHOO.widget.Calendar.MEDIUM
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.MEDIUM = "medium";
+
+/**
+* Constant used to represent single character date field string formats (e.g. M, T, W)
+* @property YAHOO.widget.Calendar.ONE_CHAR
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.Calendar.ONE_CHAR = "1char";
+
+/**
+* The set of default Config property keys and values for the Calendar
+* @property YAHOO.widget.Calendar._DEFAULT_CONFIG
+* @final
+* @static
+* @private
+* @type Object
+*/
+YAHOO.widget.Calendar._DEFAULT_CONFIG = {
+ // Default values for pagedate and selected are not class level constants - they are set during instance creation
+ PAGEDATE : {key:"pagedate", value:null},
+ SELECTED : {key:"selected", value:null},
+ TITLE : {key:"title", value:""},
+ CLOSE : {key:"close", value:false},
+ IFRAME : {key:"iframe", value:(YAHOO.env.ua.ie && YAHOO.env.ua.ie <= 6) ? true : false},
+ MINDATE : {key:"mindate", value:null},
+ MAXDATE : {key:"maxdate", value:null},
+ MULTI_SELECT : {key:"multi_select", value:false},
+ START_WEEKDAY : {key:"start_weekday", value:0},
+ SHOW_WEEKDAYS : {key:"show_weekdays", value:true},
+ SHOW_WEEK_HEADER : {key:"show_week_header", value:false},
+ SHOW_WEEK_FOOTER : {key:"show_week_footer", value:false},
+ HIDE_BLANK_WEEKS : {key:"hide_blank_weeks", value:false},
+ NAV_ARROW_LEFT: {key:"nav_arrow_left", value:null} ,
+ NAV_ARROW_RIGHT : {key:"nav_arrow_right", value:null} ,
+ MONTHS_SHORT : {key:"months_short", value:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]},
+ MONTHS_LONG: {key:"months_long", value:["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]},
+ WEEKDAYS_1CHAR: {key:"weekdays_1char", value:["S", "M", "T", "W", "T", "F", "S"]},
+ WEEKDAYS_SHORT: {key:"weekdays_short", value:["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]},
+ WEEKDAYS_MEDIUM: {key:"weekdays_medium", value:["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]},
+ WEEKDAYS_LONG: {key:"weekdays_long", value:["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]},
+ LOCALE_MONTHS:{key:"locale_months", value:"long"},
+ LOCALE_WEEKDAYS:{key:"locale_weekdays", value:"short"},
+ DATE_DELIMITER:{key:"date_delimiter", value:","},
+ DATE_FIELD_DELIMITER:{key:"date_field_delimiter", value:"/"},
+ DATE_RANGE_DELIMITER:{key:"date_range_delimiter", value:"-"},
+ MY_MONTH_POSITION:{key:"my_month_position", value:1},
+ MY_YEAR_POSITION:{key:"my_year_position", value:2},
+ MD_MONTH_POSITION:{key:"md_month_position", value:1},
+ MD_DAY_POSITION:{key:"md_day_position", value:2},
+ MDY_MONTH_POSITION:{key:"mdy_month_position", value:1},
+ MDY_DAY_POSITION:{key:"mdy_day_position", value:2},
+ MDY_YEAR_POSITION:{key:"mdy_year_position", value:3},
+ MY_LABEL_MONTH_POSITION:{key:"my_label_month_position", value:1},
+ MY_LABEL_YEAR_POSITION:{key:"my_label_year_position", value:2},
+ MY_LABEL_MONTH_SUFFIX:{key:"my_label_month_suffix", value:" "},
+ MY_LABEL_YEAR_SUFFIX:{key:"my_label_year_suffix", value:""},
+ NAV: {key:"navigator", value: null}
+};
+
+/**
+* The set of Custom Event types supported by the Calendar
+* @property YAHOO.widget.Calendar._EVENT_TYPES
+* @final
+* @static
+* @private
+* @type Object
+*/
+YAHOO.widget.Calendar._EVENT_TYPES = {
+ BEFORE_SELECT : "beforeSelect",
+ SELECT : "select",
+ BEFORE_DESELECT : "beforeDeselect",
+ DESELECT : "deselect",
+ CHANGE_PAGE : "changePage",
+ BEFORE_RENDER : "beforeRender",
+ RENDER : "render",
+ RESET : "reset",
+ CLEAR : "clear",
+ BEFORE_HIDE : "beforeHide",
+ HIDE : "hide",
+ BEFORE_SHOW : "beforeShow",
+ SHOW : "show",
+ BEFORE_HIDE_NAV : "beforeHideNav",
+ HIDE_NAV : "hideNav",
+ BEFORE_SHOW_NAV : "beforeShowNav",
+ SHOW_NAV : "showNav",
+ BEFORE_RENDER_NAV : "beforeRenderNav",
+ RENDER_NAV : "renderNav"
+};
+
+/**
+* The set of default style constants for the Calendar
+* @property YAHOO.widget.Calendar._STYLES
+* @final
+* @static
+* @private
+* @type Object
+*/
+YAHOO.widget.Calendar._STYLES = {
+ CSS_ROW_HEADER: "calrowhead",
+ CSS_ROW_FOOTER: "calrowfoot",
+ CSS_CELL : "calcell",
+ CSS_CELL_SELECTOR : "selector",
+ CSS_CELL_SELECTED : "selected",
+ CSS_CELL_SELECTABLE : "selectable",
+ CSS_CELL_RESTRICTED : "restricted",
+ CSS_CELL_TODAY : "today",
+ CSS_CELL_OOM : "oom",
+ CSS_CELL_OOB : "previous",
+ CSS_HEADER : "calheader",
+ CSS_HEADER_TEXT : "calhead",
+ CSS_BODY : "calbody",
+ CSS_WEEKDAY_CELL : "calweekdaycell",
+ CSS_WEEKDAY_ROW : "calweekdayrow",
+ CSS_FOOTER : "calfoot",
+ CSS_CALENDAR : "yui-calendar",
+ CSS_SINGLE : "single",
+ CSS_CONTAINER : "yui-calcontainer",
+ CSS_NAV_LEFT : "calnavleft",
+ CSS_NAV_RIGHT : "calnavright",
+ CSS_NAV : "calnav",
+ CSS_CLOSE : "calclose",
+ CSS_CELL_TOP : "calcelltop",
+ CSS_CELL_LEFT : "calcellleft",
+ CSS_CELL_RIGHT : "calcellright",
+ CSS_CELL_BOTTOM : "calcellbottom",
+ CSS_CELL_HOVER : "calcellhover",
+ CSS_CELL_HIGHLIGHT1 : "highlight1",
+ CSS_CELL_HIGHLIGHT2 : "highlight2",
+ CSS_CELL_HIGHLIGHT3 : "highlight3",
+ CSS_CELL_HIGHLIGHT4 : "highlight4"
+};
+
+YAHOO.widget.Calendar.prototype = {
+
+ /**
+ * The configuration object used to set up the calendars various locale and style options.
+ * @property Config
+ * @private
+ * @deprecated Configuration properties should be set by calling Calendar.cfg.setProperty.
+ * @type Object
+ */
+ Config : null,
+
+ /**
+ * The parent CalendarGroup, only to be set explicitly by the parent group
+ * @property parent
+ * @type CalendarGroup
+ */
+ parent : null,
+
+ /**
+ * The index of this item in the parent group
+ * @property index
+ * @type Number
+ */
+ index : -1,
+
+ /**
+ * The collection of calendar table cells
+ * @property cells
+ * @type HTMLTableCellElement[]
+ */
+ cells : null,
+
+ /**
+ * The collection of calendar cell dates that is parallel to the cells collection. The array contains dates field arrays in the format of [YYYY, M, D].
+ * @property cellDates
+ * @type Array[](Number[])
+ */
+ cellDates : null,
+
+ /**
+ * The id that uniquely identifies this Calendar.
+ * @property id
+ * @type String
+ */
+ id : null,
+
+ /**
+ * The unique id associated with the Calendar's container
+ * @property containerId
+ * @type String
+ */
+ containerId: null,
+
+ /**
+ * The DOM element reference that points to this calendar's container element. The calendar will be inserted into this element when the shell is rendered.
+ * @property oDomContainer
+ * @type HTMLElement
+ */
+ oDomContainer : null,
+
+ /**
+ * A Date object representing today's date.
+ * @property today
+ * @type Date
+ */
+ today : null,
+
+ /**
+ * The list of render functions, along with required parameters, used to render cells.
+ * @property renderStack
+ * @type Array[]
+ */
+ renderStack : null,
+
+ /**
+ * A copy of the initial render functions created before rendering.
+ * @property _renderStack
+ * @private
+ * @type Array
+ */
+ _renderStack : null,
+
+ /**
+ * A reference to the CalendarNavigator instance created for this Calendar.
+ * Will be null if the "navigator" configuration property has not been set
+ * @property oNavigator
+ * @type CalendarNavigator
+ */
+ oNavigator : null,
+
+ /**
+ * The private list of initially selected dates.
+ * @property _selectedDates
+ * @private
+ * @type Array
+ */
+ _selectedDates : null,
+
+ /**
+ * A map of DOM event handlers to attach to cells associated with specific CSS class names
+ * @property domEventMap
+ * @type Object
+ */
+ domEventMap : null,
+
+ /**
+ * Protected helper used to parse Calendar constructor/init arguments.
+ *
+ * As of 2.4.0, Calendar supports a simpler constructor
+ * signature. This method reconciles arguments
+ * received in the pre 2.4.0 and 2.4.0 formats.
+ *
+ * @protected
+ * @method _parseArgs
+ * @param {Array} Function "arguments" array
+ * @return {Object} Object with id, container, config properties containing
+ * the reconciled argument values.
+ **/
+ _parseArgs : function(args) {
+ /*
+ 2.4.0 Constructors signatures
+
+ new Calendar(String)
+ new Calendar(HTMLElement)
+ new Calendar(String, ConfigObject)
+ new Calendar(HTMLElement, ConfigObject)
+
+ Pre 2.4.0 Constructor signatures
+
+ new Calendar(String, String)
+ new Calendar(String, HTMLElement)
+ new Calendar(String, String, ConfigObject)
+ new Calendar(String, HTMLElement, ConfigObject)
+ */
+ var nArgs = {id:null, container:null, config:null};
+
+ if (args && args.length && args.length > 0) {
+ switch (args.length) {
+ case 1:
+ nArgs.id = null;
+ nArgs.container = args[0];
+ nArgs.config = null;
+ break;
+ case 2:
+ if (YAHOO.lang.isObject(args[1]) && !args[1].tagName && !(args[1] instanceof String)) {
+ nArgs.id = null;
+ nArgs.container = args[0];
+ nArgs.config = args[1];
+ } else {
+ nArgs.id = args[0];
+ nArgs.container = args[1];
+ nArgs.config = null;
+ }
+ break;
+ default: // 3+
+ nArgs.id = args[0];
+ nArgs.container = args[1];
+ nArgs.config = args[2];
+ break;
+ }
+ } else {
+ }
+ return nArgs;
+ },
+
+ /**
+ * Initializes the Calendar widget.
+ * @method init
+ *
+ * @param {String} id optional The id of the table element that will represent the Calendar widget. As of 2.4.0, this argument is optional.
+ * @param {String | HTMLElement} container The id of the container div element that will wrap the Calendar table, or a reference to a DIV element which exists in the document.
+ * @param {Object} config optional The configuration object containing the initial configuration values for the Calendar.
+ */
+ init : function(id, container, config) {
+ // Normalize 2.4.0, pre 2.4.0 args
+ var nArgs = this._parseArgs(arguments);
+
+ id = nArgs.id;
+ container = nArgs.container;
+ config = nArgs.config;
+
+ this.oDomContainer = YAHOO.util.Dom.get(container);
+
+ if (!this.oDomContainer.id) {
+ this.oDomContainer.id = YAHOO.util.Dom.generateId();
+ }
+ if (!id) {
+ id = this.oDomContainer.id + "_t";
+ }
+
+ this.id = id;
+ this.containerId = this.oDomContainer.id;
+
+ this.initEvents();
+
+ this.today = new Date();
+ YAHOO.widget.DateMath.clearTime(this.today);
+
+ /**
+ * The Config object used to hold the configuration variables for the Calendar
+ * @property cfg
+ * @type YAHOO.util.Config
+ */
+ this.cfg = new YAHOO.util.Config(this);
+
+ /**
+ * The local object which contains the Calendar's options
+ * @property Options
+ * @type Object
+ */
+ this.Options = {};
+
+ /**
+ * The local object which contains the Calendar's locale settings
+ * @property Locale
+ * @type Object
+ */
+ this.Locale = {};
+
+ this.initStyles();
+
+ YAHOO.util.Dom.addClass(this.oDomContainer, this.Style.CSS_CONTAINER);
+ YAHOO.util.Dom.addClass(this.oDomContainer, this.Style.CSS_SINGLE);
+
+ this.cellDates = [];
+ this.cells = [];
+ this.renderStack = [];
+ this._renderStack = [];
+
+ this.setupConfig();
+
+ if (config) {
+ this.cfg.applyConfig(config, true);
+ }
+
+ this.cfg.fireQueue();
+ },
+
+ /**
+ * Default Config listener for the iframe property. If the iframe config property is set to true,
+ * renders the built-in IFRAME shim if the container is relatively or absolutely positioned.
+ *
+ * @method configIframe
+ */
+ configIframe : function(type, args, obj) {
+ var useIframe = args[0];
+
+ if (!this.parent) {
+ if (YAHOO.util.Dom.inDocument(this.oDomContainer)) {
+ if (useIframe) {
+ var pos = YAHOO.util.Dom.getStyle(this.oDomContainer, "position");
+
+ if (pos == "absolute" || pos == "relative") {
+
+ if (!YAHOO.util.Dom.inDocument(this.iframe)) {
+ this.iframe = document.createElement("iframe");
+ this.iframe.src = "javascript:false;";
+
+ YAHOO.util.Dom.setStyle(this.iframe, "opacity", "0");
+
+ if (YAHOO.env.ua.ie && YAHOO.env.ua.ie <= 6) {
+ YAHOO.util.Dom.addClass(this.iframe, "fixedsize");
+ }
+
+ this.oDomContainer.insertBefore(this.iframe, this.oDomContainer.firstChild);
+ }
+ }
+ } else {
+ if (this.iframe) {
+ if (this.iframe.parentNode) {
+ this.iframe.parentNode.removeChild(this.iframe);
+ }
+ this.iframe = null;
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Default handler for the "title" property
+ * @method configTitle
+ */
+ configTitle : function(type, args, obj) {
+ var title = args[0];
+
+ // "" disables title bar
+ if (title) {
+ this.createTitleBar(title);
+ } else {
+ var close = this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.CLOSE.key);
+ if (!close) {
+ this.removeTitleBar();
+ } else {
+ this.createTitleBar(" ");
+ }
+ }
+ },
+
+ /**
+ * Default handler for the "close" property
+ * @method configClose
+ */
+ configClose : function(type, args, obj) {
+ var close = args[0],
+ title = this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.TITLE.key);
+
+ if (close) {
+ if (!title) {
+ this.createTitleBar(" ");
+ }
+ this.createCloseButton();
+ } else {
+ this.removeCloseButton();
+ if (!title) {
+ this.removeTitleBar();
+ }
+ }
+ },
+
+ /**
+ * Initializes Calendar's built-in CustomEvents
+ * @method initEvents
+ */
+ initEvents : function() {
+
+ var defEvents = YAHOO.widget.Calendar._EVENT_TYPES;
+
+ /**
+ * Fired before a selection is made
+ * @event beforeSelectEvent
+ */
+ this.beforeSelectEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_SELECT);
+
+ /**
+ * Fired when a selection is made
+ * @event selectEvent
+ * @param {Array} Array of Date field arrays in the format [YYYY, MM, DD].
+ */
+ this.selectEvent = new YAHOO.util.CustomEvent(defEvents.SELECT);
+
+ /**
+ * Fired before a selection is made
+ * @event beforeDeselectEvent
+ */
+ this.beforeDeselectEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_DESELECT);
+
+ /**
+ * Fired when a selection is made
+ * @event deselectEvent
+ * @param {Array} Array of Date field arrays in the format [YYYY, MM, DD].
+ */
+ this.deselectEvent = new YAHOO.util.CustomEvent(defEvents.DESELECT);
+
+ /**
+ * Fired when the Calendar page is changed
+ * @event changePageEvent
+ */
+ this.changePageEvent = new YAHOO.util.CustomEvent(defEvents.CHANGE_PAGE);
+
+ /**
+ * Fired before the Calendar is rendered
+ * @event beforeRenderEvent
+ */
+ this.beforeRenderEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_RENDER);
+
+ /**
+ * Fired when the Calendar is rendered
+ * @event renderEvent
+ */
+ this.renderEvent = new YAHOO.util.CustomEvent(defEvents.RENDER);
+
+ /**
+ * Fired when the Calendar is reset
+ * @event resetEvent
+ */
+ this.resetEvent = new YAHOO.util.CustomEvent(defEvents.RESET);
+
+ /**
+ * Fired when the Calendar is cleared
+ * @event clearEvent
+ */
+ this.clearEvent = new YAHOO.util.CustomEvent(defEvents.CLEAR);
+
+ /**
+ * Fired just before the Calendar is to be shown
+ * @event beforeShowEvent
+ */
+ this.beforeShowEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_SHOW);
+
+ /**
+ * Fired after the Calendar is shown
+ * @event showEvent
+ */
+ this.showEvent = new YAHOO.util.CustomEvent(defEvents.SHOW);
+
+ /**
+ * Fired just before the Calendar is to be hidden
+ * @event beforeHideEvent
+ */
+ this.beforeHideEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_HIDE);
+
+ /**
+ * Fired after the Calendar is hidden
+ * @event hideEvent
+ */
+ this.hideEvent = new YAHOO.util.CustomEvent(defEvents.HIDE);
+
+ /**
+ * Fired just before the CalendarNavigator is to be shown
+ * @event beforeShowNavEvent
+ */
+ this.beforeShowNavEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_SHOW_NAV);
+
+ /**
+ * Fired after the CalendarNavigator is shown
+ * @event showNavEvent
+ */
+ this.showNavEvent = new YAHOO.util.CustomEvent(defEvents.SHOW_NAV);
+
+ /**
+ * Fired just before the CalendarNavigator is to be hidden
+ * @event beforeHideNavEvent
+ */
+ this.beforeHideNavEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_HIDE_NAV);
+
+ /**
+ * Fired after the CalendarNavigator is hidden
+ * @event hideNavEvent
+ */
+ this.hideNavEvent = new YAHOO.util.CustomEvent(defEvents.HIDE_NAV);
+
+ /**
+ * Fired just before the CalendarNavigator is to be rendered
+ * @event beforeRenderNavEvent
+ */
+ this.beforeRenderNavEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_RENDER_NAV);
+
+ /**
+ * Fired after the CalendarNavigator is rendered
+ * @event renderNavEvent
+ */
+ this.renderNavEvent = new YAHOO.util.CustomEvent(defEvents.RENDER_NAV);
+
+ this.beforeSelectEvent.subscribe(this.onBeforeSelect, this, true);
+ this.selectEvent.subscribe(this.onSelect, this, true);
+ this.beforeDeselectEvent.subscribe(this.onBeforeDeselect, this, true);
+ this.deselectEvent.subscribe(this.onDeselect, this, true);
+ this.changePageEvent.subscribe(this.onChangePage, this, true);
+ this.renderEvent.subscribe(this.onRender, this, true);
+ this.resetEvent.subscribe(this.onReset, this, true);
+ this.clearEvent.subscribe(this.onClear, this, true);
+ },
+
+ /**
+ * The default event function that is attached to a date link within a calendar cell
+ * when the calendar is rendered.
+ * @method doSelectCell
+ * @param {DOMEvent} e The event
+ * @param {Calendar} cal A reference to the calendar passed by the Event utility
+ */
+ doSelectCell : function(e, cal) {
+ var cell,index,d,date;
+
+ var target = YAHOO.util.Event.getTarget(e);
+ var tagName = target.tagName.toLowerCase();
+ var defSelector = false;
+
+ while (tagName != "td" && ! YAHOO.util.Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
+
+ if (!defSelector && tagName == "a" && YAHOO.util.Dom.hasClass(target, cal.Style.CSS_CELL_SELECTOR)) {
+ defSelector = true;
+ }
+
+ target = target.parentNode;
+ tagName = target.tagName.toLowerCase();
+ // TODO: No need to go all the way up to html.
+ if (tagName == "html") {
+ return;
+ }
+ }
+
+ if (defSelector) {
+ // Stop link href navigation for default renderer
+ YAHOO.util.Event.preventDefault(e);
+ }
+
+ cell = target;
+
+ if (YAHOO.util.Dom.hasClass(cell, cal.Style.CSS_CELL_SELECTABLE)) {
+ index = cell.id.split("cell")[1];
+ d = cal.cellDates[index];
+ date = YAHOO.widget.DateMath.getDate(d[0],d[1]-1,d[2]);
+
+ var link;
+
+ if (cal.Options.MULTI_SELECT) {
+ link = cell.getElementsByTagName("a")[0];
+ if (link) {
+ link.blur();
+ }
+
+ var cellDate = cal.cellDates[index];
+ var cellDateIndex = cal._indexOfSelectedFieldArray(cellDate);
+
+ if (cellDateIndex > -1) {
+ cal.deselectCell(index);
+ } else {
+ cal.selectCell(index);
+ }
+
+ } else {
+ link = cell.getElementsByTagName("a")[0];
+ if (link) {
+ link.blur();
+ }
+ cal.selectCell(index);
+ }
+ }
+ },
+
+ /**
+ * The event that is executed when the user hovers over a cell
+ * @method doCellMouseOver
+ * @param {DOMEvent} e The event
+ * @param {Calendar} cal A reference to the calendar passed by the Event utility
+ */
+ doCellMouseOver : function(e, cal) {
+ var target;
+ if (e) {
+ target = YAHOO.util.Event.getTarget(e);
+ } else {
+ target = this;
+ }
+
+ while (target.tagName && target.tagName.toLowerCase() != "td") {
+ target = target.parentNode;
+ if (!target.tagName || target.tagName.toLowerCase() == "html") {
+ return;
+ }
+ }
+
+ if (YAHOO.util.Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
+ YAHOO.util.Dom.addClass(target, cal.Style.CSS_CELL_HOVER);
+ }
+ },
+
+ /**
+ * The event that is executed when the user moves the mouse out of a cell
+ * @method doCellMouseOut
+ * @param {DOMEvent} e The event
+ * @param {Calendar} cal A reference to the calendar passed by the Event utility
+ */
+ doCellMouseOut : function(e, cal) {
+ var target;
+ if (e) {
+ target = YAHOO.util.Event.getTarget(e);
+ } else {
+ target = this;
+ }
+
+ while (target.tagName && target.tagName.toLowerCase() != "td") {
+ target = target.parentNode;
+ if (!target.tagName || target.tagName.toLowerCase() == "html") {
+ return;
+ }
+ }
+
+ if (YAHOO.util.Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
+ YAHOO.util.Dom.removeClass(target, cal.Style.CSS_CELL_HOVER);
+ }
+ },
+
+ setupConfig : function() {
+
+ var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG;
+
+ /**
+ * The month/year representing the current visible Calendar date (mm/yyyy)
+ * @config pagedate
+ * @type String
+ * @default today's date
+ */
+ this.cfg.addProperty(defCfg.PAGEDATE.key, { value:new Date(), handler:this.configPageDate } );
+
+ /**
+ * The date or range of dates representing the current Calendar selection
+ * @config selected
+ * @type String
+ * @default []
+ */
+ this.cfg.addProperty(defCfg.SELECTED.key, { value:[], handler:this.configSelected } );
+
+ /**
+ * The title to display above the Calendar's month header
+ * @config title
+ * @type String
+ * @default ""
+ */
+ this.cfg.addProperty(defCfg.TITLE.key, { value:defCfg.TITLE.value, handler:this.configTitle } );
+
+ /**
+ * Whether or not a close button should be displayed for this Calendar
+ * @config close
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.CLOSE.key, { value:defCfg.CLOSE.value, handler:this.configClose } );
+
+ /**
+ * Whether or not an iframe shim should be placed under the Calendar to prevent select boxes from bleeding through in Internet Explorer 6 and below.
+ * This property is enabled by default for IE6 and below. It is disabled by default for other browsers for performance reasons, but can be
+ * enabled if required.
+ *
+ * @config iframe
+ * @type Boolean
+ * @default true for IE6 and below, false for all other browsers
+ */
+ this.cfg.addProperty(defCfg.IFRAME.key, { value:defCfg.IFRAME.value, handler:this.configIframe, validator:this.cfg.checkBoolean } );
+
+ /**
+ * The minimum selectable date in the current Calendar (mm/dd/yyyy)
+ * @config mindate
+ * @type String
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.MINDATE.key, { value:defCfg.MINDATE.value, handler:this.configMinDate } );
+
+ /**
+ * The maximum selectable date in the current Calendar (mm/dd/yyyy)
+ * @config maxdate
+ * @type String
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.MAXDATE.key, { value:defCfg.MAXDATE.value, handler:this.configMaxDate } );
+
+
+ // Options properties
+
+ /**
+ * True if the Calendar should allow multiple selections. False by default.
+ * @config MULTI_SELECT
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.MULTI_SELECT.key, { value:defCfg.MULTI_SELECT.value, handler:this.configOptions, validator:this.cfg.checkBoolean } );
+
+ /**
+ * The weekday the week begins on. Default is 0 (Sunday = 0, Monday = 1 ... Saturday = 6).
+ * @config START_WEEKDAY
+ * @type number
+ * @default 0
+ */
+ this.cfg.addProperty(defCfg.START_WEEKDAY.key, { value:defCfg.START_WEEKDAY.value, handler:this.configOptions, validator:this.cfg.checkNumber } );
+
+ /**
+ * True if the Calendar should show weekday labels. True by default.
+ * @config SHOW_WEEKDAYS
+ * @type Boolean
+ * @default true
+ */
+ this.cfg.addProperty(defCfg.SHOW_WEEKDAYS.key, { value:defCfg.SHOW_WEEKDAYS.value, handler:this.configOptions, validator:this.cfg.checkBoolean } );
+
+ /**
+ * True if the Calendar should show week row headers. False by default.
+ * @config SHOW_WEEK_HEADER
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.SHOW_WEEK_HEADER.key, { value:defCfg.SHOW_WEEK_HEADER.value, handler:this.configOptions, validator:this.cfg.checkBoolean } );
+
+ /**
+ * True if the Calendar should show week row footers. False by default.
+ * @config SHOW_WEEK_FOOTER
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.SHOW_WEEK_FOOTER.key,{ value:defCfg.SHOW_WEEK_FOOTER.value, handler:this.configOptions, validator:this.cfg.checkBoolean } );
+
+ /**
+ * True if the Calendar should suppress weeks that are not a part of the current month. False by default.
+ * @config HIDE_BLANK_WEEKS
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.HIDE_BLANK_WEEKS.key, { value:defCfg.HIDE_BLANK_WEEKS.value, handler:this.configOptions, validator:this.cfg.checkBoolean } );
+
+ /**
+ * The image that should be used for the left navigation arrow.
+ * @config NAV_ARROW_LEFT
+ * @type String
+ * @deprecated You can customize the image by overriding the default CSS class for the left arrow - "calnavleft"
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.NAV_ARROW_LEFT.key, { value:defCfg.NAV_ARROW_LEFT.value, handler:this.configOptions } );
+
+ /**
+ * The image that should be used for the right navigation arrow.
+ * @config NAV_ARROW_RIGHT
+ * @type String
+ * @deprecated You can customize the image by overriding the default CSS class for the right arrow - "calnavright"
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.NAV_ARROW_RIGHT.key, { value:defCfg.NAV_ARROW_RIGHT.value, handler:this.configOptions } );
+
+ // Locale properties
+
+ /**
+ * The short month labels for the current locale.
+ * @config MONTHS_SHORT
+ * @type String[]
+ * @default ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+ */
+ this.cfg.addProperty(defCfg.MONTHS_SHORT.key, { value:defCfg.MONTHS_SHORT.value, handler:this.configLocale } );
+
+ /**
+ * The long month labels for the current locale.
+ * @config MONTHS_LONG
+ * @type String[]
+ * @default ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
+ */
+ this.cfg.addProperty(defCfg.MONTHS_LONG.key, { value:defCfg.MONTHS_LONG.value, handler:this.configLocale } );
+
+ /**
+ * The 1-character weekday labels for the current locale.
+ * @config WEEKDAYS_1CHAR
+ * @type String[]
+ * @default ["S", "M", "T", "W", "T", "F", "S"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_1CHAR.key, { value:defCfg.WEEKDAYS_1CHAR.value, handler:this.configLocale } );
+
+ /**
+ * The short weekday labels for the current locale.
+ * @config WEEKDAYS_SHORT
+ * @type String[]
+ * @default ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_SHORT.key, { value:defCfg.WEEKDAYS_SHORT.value, handler:this.configLocale } );
+
+ /**
+ * The medium weekday labels for the current locale.
+ * @config WEEKDAYS_MEDIUM
+ * @type String[]
+ * @default ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_MEDIUM.key, { value:defCfg.WEEKDAYS_MEDIUM.value, handler:this.configLocale } );
+
+ /**
+ * The long weekday labels for the current locale.
+ * @config WEEKDAYS_LONG
+ * @type String[]
+ * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_LONG.key, { value:defCfg.WEEKDAYS_LONG.value, handler:this.configLocale } );
+
+ /**
+ * Refreshes the locale values used to build the Calendar.
+ * @method refreshLocale
+ * @private
+ */
+ var refreshLocale = function() {
+ this.cfg.refireEvent(defCfg.LOCALE_MONTHS.key);
+ this.cfg.refireEvent(defCfg.LOCALE_WEEKDAYS.key);
+ };
+
+ this.cfg.subscribeToConfigEvent(defCfg.START_WEEKDAY.key, refreshLocale, this, true);
+ this.cfg.subscribeToConfigEvent(defCfg.MONTHS_SHORT.key, refreshLocale, this, true);
+ this.cfg.subscribeToConfigEvent(defCfg.MONTHS_LONG.key, refreshLocale, this, true);
+ this.cfg.subscribeToConfigEvent(defCfg.WEEKDAYS_1CHAR.key, refreshLocale, this, true);
+ this.cfg.subscribeToConfigEvent(defCfg.WEEKDAYS_SHORT.key, refreshLocale, this, true);
+ this.cfg.subscribeToConfigEvent(defCfg.WEEKDAYS_MEDIUM.key, refreshLocale, this, true);
+ this.cfg.subscribeToConfigEvent(defCfg.WEEKDAYS_LONG.key, refreshLocale, this, true);
+
+ /**
+ * The setting that determines which length of month labels should be used. Possible values are "short" and "long".
+ * @config LOCALE_MONTHS
+ * @type String
+ * @default "long"
+ */
+ this.cfg.addProperty(defCfg.LOCALE_MONTHS.key, { value:defCfg.LOCALE_MONTHS.value, handler:this.configLocaleValues } );
+
+ /**
+ * The setting that determines which length of weekday labels should be used. Possible values are "1char", "short", "medium", and "long".
+ * @config LOCALE_WEEKDAYS
+ * @type String
+ * @default "short"
+ */
+ this.cfg.addProperty(defCfg.LOCALE_WEEKDAYS.key, { value:defCfg.LOCALE_WEEKDAYS.value, handler:this.configLocaleValues } );
+
+ /**
+ * The value used to delimit individual dates in a date string passed to various Calendar functions.
+ * @config DATE_DELIMITER
+ * @type String
+ * @default ","
+ */
+ this.cfg.addProperty(defCfg.DATE_DELIMITER.key, { value:defCfg.DATE_DELIMITER.value, handler:this.configLocale } );
+
+ /**
+ * The value used to delimit date fields in a date string passed to various Calendar functions.
+ * @config DATE_FIELD_DELIMITER
+ * @type String
+ * @default "/"
+ */
+ this.cfg.addProperty(defCfg.DATE_FIELD_DELIMITER.key, { value:defCfg.DATE_FIELD_DELIMITER.value, handler:this.configLocale } );
+
+ /**
+ * The value used to delimit date ranges in a date string passed to various Calendar functions.
+ * @config DATE_RANGE_DELIMITER
+ * @type String
+ * @default "-"
+ */
+ this.cfg.addProperty(defCfg.DATE_RANGE_DELIMITER.key, { value:defCfg.DATE_RANGE_DELIMITER.value, handler:this.configLocale } );
+
+ /**
+ * The position of the month in a month/year date string
+ * @config MY_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MY_MONTH_POSITION.key, { value:defCfg.MY_MONTH_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the year in a month/year date string
+ * @config MY_YEAR_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MY_YEAR_POSITION.key, { value:defCfg.MY_YEAR_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the month in a month/day date string
+ * @config MD_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MD_MONTH_POSITION.key, { value:defCfg.MD_MONTH_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the day in a month/year date string
+ * @config MD_DAY_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MD_DAY_POSITION.key, { value:defCfg.MD_DAY_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the month in a month/day/year date string
+ * @config MDY_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MDY_MONTH_POSITION.key, { value:defCfg.MDY_MONTH_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the day in a month/day/year date string
+ * @config MDY_DAY_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MDY_DAY_POSITION.key, { value:defCfg.MDY_DAY_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the year in a month/day/year date string
+ * @config MDY_YEAR_POSITION
+ * @type Number
+ * @default 3
+ */
+ this.cfg.addProperty(defCfg.MDY_YEAR_POSITION.key, { value:defCfg.MDY_YEAR_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the month in the month year label string used as the Calendar header
+ * @config MY_LABEL_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_MONTH_POSITION.key, { value:defCfg.MY_LABEL_MONTH_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the year in the month year label string used as the Calendar header
+ * @config MY_LABEL_YEAR_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_YEAR_POSITION.key, { value:defCfg.MY_LABEL_YEAR_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } );
+
+ /**
+ * The suffix used after the month when rendering the Calendar header
+ * @config MY_LABEL_MONTH_SUFFIX
+ * @type String
+ * @default " "
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_MONTH_SUFFIX.key, { value:defCfg.MY_LABEL_MONTH_SUFFIX.value, handler:this.configLocale } );
+
+ /**
+ * The suffix used after the year when rendering the Calendar header
+ * @config MY_LABEL_YEAR_SUFFIX
+ * @type String
+ * @default ""
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_YEAR_SUFFIX.key, { value:defCfg.MY_LABEL_YEAR_SUFFIX.value, handler:this.configLocale } );
+
+ /**
+ * Configuration for the Month/Year CalendarNavigator UI which allows the user to jump directly to a
+ * specific Month/Year without having to scroll sequentially through months.
+ *
+ * Setting this property to null (default value) or false, will disable the CalendarNavigator UI.
+ *
+ *
+ * Setting this property to true will enable the CalendarNavigatior UI with the default CalendarNavigator configuration values.
+ *
+ *
+ * This property can also be set to an object literal containing configuration properties for the CalendarNavigator UI.
+ * The configuration object expects the the following case-sensitive properties, with the "strings" property being a nested object.
+ * Any properties which are not provided will use the default values (defined in the CalendarNavigator class).
+ *
+ *
+ *
strings
+ *
Object : An object with the properties shown below, defining the string labels to use in the Navigator's UI
+ *
+ *
month
String : The string to use for the month label. Defaults to "Month".
+ *
year
String : The string to use for the year label. Defaults to "Year".
+ *
submit
String : The string to use for the submit button label. Defaults to "Okay".
+ *
cancel
String : The string to use for the cancel button label. Defaults to "Cancel".
+ *
invalidYear
String : The string to use for invalid year values. Defaults to "Year needs to be a number".
+ *
+ *
+ *
monthFormat
String : The month format to use. Either YAHOO.widget.Calendar.LONG, or YAHOO.widget.Calendar.SHORT. Defaults to YAHOO.widget.Calendar.LONG
+ *
initialFocus
String : Either "year" or "month" specifying which input control should get initial focus. Defaults to "year"
';
+
+ return html;
+ },
+
+ /**
+ * Renders the calendar body.
+ * @method renderBody
+ * @param {Date} workingDate The current working Date being used for the render process
+ * @param {Array} html The current working HTML array
+ * @return {Array} The current working HTML array
+ */
+ renderBody : function(workingDate, html) {
+
+ var DM = YAHOO.widget.DateMath,
+ CAL = YAHOO.widget.Calendar,
+ D = YAHOO.util.Dom,
+ defCfg = CAL._DEFAULT_CONFIG;
+
+ var startDay = this.cfg.getProperty(defCfg.START_WEEKDAY.key);
+
+ this.preMonthDays = workingDate.getDay();
+ if (startDay > 0) {
+ this.preMonthDays -= startDay;
+ }
+ if (this.preMonthDays < 0) {
+ this.preMonthDays += 7;
+ }
+
+ this.monthDays = DM.findMonthEnd(workingDate).getDate();
+ this.postMonthDays = CAL.DISPLAY_DAYS-this.preMonthDays-this.monthDays;
+
+
+ workingDate = DM.subtract(workingDate, DM.DAY, this.preMonthDays);
+
+ var weekNum,
+ weekClass,
+ weekPrefix = "w",
+ cellPrefix = "_cell",
+ workingDayPrefix = "wd",
+ dayPrefix = "d",
+ cellRenderers,
+ renderer,
+ todayYear = this.today.getFullYear(),
+ todayMonth = this.today.getMonth(),
+ todayDate = this.today.getDate(),
+ useDate = this.cfg.getProperty(defCfg.PAGEDATE.key),
+ hideBlankWeeks = this.cfg.getProperty(defCfg.HIDE_BLANK_WEEKS.key),
+ showWeekFooter = this.cfg.getProperty(defCfg.SHOW_WEEK_FOOTER.key),
+ showWeekHeader = this.cfg.getProperty(defCfg.SHOW_WEEK_HEADER.key),
+ mindate = this.cfg.getProperty(defCfg.MINDATE.key),
+ maxdate = this.cfg.getProperty(defCfg.MAXDATE.key);
+
+ if (mindate) {
+ mindate = DM.clearTime(mindate);
+ }
+ if (maxdate) {
+ maxdate = DM.clearTime(maxdate);
+ }
+
+ html[html.length] = '';
+
+ var i = 0,
+ tempDiv = document.createElement("div"),
+ cell = document.createElement("td");
+
+ tempDiv.appendChild(cell);
+
+ var cal = this.parent || this;
+
+ for (var r=0;r<6;r++) {
+ weekNum = DM.getWeekNumber(workingDate, startDay);
+ weekClass = weekPrefix + weekNum;
+
+ // Local OOM check for performance, since we already have pagedate
+ if (r !== 0 && hideBlankWeeks === true && workingDate.getMonth() != useDate.getMonth()) {
+ break;
+ } else {
+ html[html.length] = '
';
+
+ if (showWeekHeader) { html = this.renderRowHeader(weekNum, html); }
+
+ for (var d=0; d < 7; d++){ // Render actual days
+
+ cellRenderers = [];
+
+ this.clearElement(cell);
+ cell.className = this.Style.CSS_CELL;
+ cell.id = this.id + cellPrefix + i;
+
+ if (workingDate.getDate() == todayDate &&
+ workingDate.getMonth() == todayMonth &&
+ workingDate.getFullYear() == todayYear) {
+ cellRenderers[cellRenderers.length]=cal.renderCellStyleToday;
+ }
+
+ var workingArray = [workingDate.getFullYear(),workingDate.getMonth()+1,workingDate.getDate()];
+ this.cellDates[this.cellDates.length] = workingArray; // Add this date to cellDates
+
+ // Local OOM check for performance, since we already have pagedate
+ if (workingDate.getMonth() != useDate.getMonth()) {
+ cellRenderers[cellRenderers.length]=cal.renderCellNotThisMonth;
+ } else {
+ D.addClass(cell, workingDayPrefix + workingDate.getDay());
+ D.addClass(cell, dayPrefix + workingDate.getDate());
+
+ for (var s=0;s= d1.getTime() && workingDate.getTime() <= d2.getTime()) {
+ renderer = rArray[2];
+
+ if (workingDate.getTime()==d2.getTime()) {
+ this.renderStack.splice(s,1);
+ }
+ }
+ break;
+ case CAL.WEEKDAY:
+ var weekday = rArray[1][0];
+ if (workingDate.getDay()+1 == weekday) {
+ renderer = rArray[2];
+ }
+ break;
+ case CAL.MONTH:
+ month = rArray[1][0];
+ if (workingDate.getMonth()+1 == month) {
+ renderer = rArray[2];
+ }
+ break;
+ }
+
+ if (renderer) {
+ cellRenderers[cellRenderers.length]=renderer;
+ }
+ }
+
+ }
+
+ if (this._indexOfSelectedFieldArray(workingArray) > -1) {
+ cellRenderers[cellRenderers.length]=cal.renderCellStyleSelected;
+ }
+
+ if ((mindate && (workingDate.getTime() < mindate.getTime())) ||
+ (maxdate && (workingDate.getTime() > maxdate.getTime()))
+ ) {
+ cellRenderers[cellRenderers.length]=cal.renderOutOfBoundsDate;
+ } else {
+ cellRenderers[cellRenderers.length]=cal.styleCellDefault;
+ cellRenderers[cellRenderers.length]=cal.renderCellDefault;
+ }
+
+ for (var x=0; x < cellRenderers.length; ++x) {
+ if (cellRenderers[x].call(cal, workingDate, cell) == CAL.STOP_RENDER) {
+ break;
+ }
+ }
+
+ workingDate.setTime(workingDate.getTime() + DM.ONE_DAY_MS);
+ // Just in case we crossed DST/Summertime boundaries
+ workingDate = DM.clearTime(workingDate);
+
+ if (i >= 0 && i <= 6) {
+ D.addClass(cell, this.Style.CSS_CELL_TOP);
+ }
+ if ((i % 7) === 0) {
+ D.addClass(cell, this.Style.CSS_CELL_LEFT);
+ }
+ if (((i+1) % 7) === 0) {
+ D.addClass(cell, this.Style.CSS_CELL_RIGHT);
+ }
+
+ var postDays = this.postMonthDays;
+ if (hideBlankWeeks && postDays >= 7) {
+ var blankWeeks = Math.floor(postDays/7);
+ for (var p=0;p= ((this.preMonthDays+postDays+this.monthDays)-7)) {
+ D.addClass(cell, this.Style.CSS_CELL_BOTTOM);
+ }
+
+ html[html.length] = tempDiv.innerHTML;
+ i++;
+ }
+
+ if (showWeekFooter) { html = this.renderRowFooter(weekNum, html); }
+
+ html[html.length] = '
';
+ }
+ }
+
+ html[html.length] = '';
+
+ return html;
+ },
+
+ /**
+ * Renders the calendar footer. In the default implementation, there is
+ * no footer.
+ * @method renderFooter
+ * @param {Array} html The current working HTML array
+ * @return {Array} The current working HTML array
+ */
+ renderFooter : function(html) { return html; },
+
+ /**
+ * Renders the calendar after it has been configured. The render() method has a specific call chain that will execute
+ * when the method is called: renderHeader, renderBody, renderFooter.
+ * Refer to the documentation for those methods for information on
+ * individual render tasks.
+ * @method render
+ */
+ render : function() {
+ this.beforeRenderEvent.fire();
+
+ var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG;
+
+ // Find starting day of the current month
+ var workingDate = YAHOO.widget.DateMath.findMonthStart(this.cfg.getProperty(defCfg.PAGEDATE.key));
+
+ this.resetRenderers();
+ this.cellDates.length = 0;
+
+ YAHOO.util.Event.purgeElement(this.oDomContainer, true);
+
+ var html = [];
+
+ html[html.length] = '
';
+ html = this.renderHeader(html);
+ html = this.renderBody(workingDate, html);
+ html = this.renderFooter(html);
+ html[html.length] = '
';
+
+ this.oDomContainer.innerHTML = html.join("\n");
+
+ this.applyListeners();
+ this.cells = this.oDomContainer.getElementsByTagName("td");
+
+ this.cfg.refireEvent(defCfg.TITLE.key);
+ this.cfg.refireEvent(defCfg.CLOSE.key);
+ this.cfg.refireEvent(defCfg.IFRAME.key);
+
+ this.renderEvent.fire();
+ },
+
+ /**
+ * Applies the Calendar's DOM listeners to applicable elements.
+ * @method applyListeners
+ */
+ applyListeners : function() {
+ var root = this.oDomContainer;
+ var cal = this.parent || this;
+ var anchor = "a";
+ var mousedown = "mousedown";
+
+ var linkLeft = YAHOO.util.Dom.getElementsByClassName(this.Style.CSS_NAV_LEFT, anchor, root);
+ var linkRight = YAHOO.util.Dom.getElementsByClassName(this.Style.CSS_NAV_RIGHT, anchor, root);
+
+ if (linkLeft && linkLeft.length > 0) {
+ this.linkLeft = linkLeft[0];
+ YAHOO.util.Event.addListener(this.linkLeft, mousedown, cal.previousMonth, cal, true);
+ }
+
+ if (linkRight && linkRight.length > 0) {
+ this.linkRight = linkRight[0];
+ YAHOO.util.Event.addListener(this.linkRight, mousedown, cal.nextMonth, cal, true);
+ }
+
+ if (cal.cfg.getProperty("navigator") !== null) {
+ this.applyNavListeners();
+ }
+
+ if (this.domEventMap) {
+ var el,elements;
+ for (var cls in this.domEventMap) {
+ if (YAHOO.lang.hasOwnProperty(this.domEventMap, cls)) {
+ var items = this.domEventMap[cls];
+
+ if (! (items instanceof Array)) {
+ items = [items];
+ }
+
+ for (var i=0;i 0) {
+
+ function show(e, obj) {
+ var target = E.getTarget(e);
+ // this == navBtn
+ if (this === target || YAHOO.util.Dom.isAncestor(this, target)) {
+ E.preventDefault(e);
+ }
+ var navigator = calParent.oNavigator;
+ if (navigator) {
+ var pgdate = cal.cfg.getProperty("pagedate");
+ navigator.setYear(pgdate.getFullYear());
+ navigator.setMonth(pgdate.getMonth());
+ navigator.show();
+ }
+ }
+ E.addListener(navBtns, "click", show);
+ }
+ },
+
+ /**
+ * Retrieves the Date object for the specified Calendar cell
+ * @method getDateByCellId
+ * @param {String} id The id of the cell
+ * @return {Date} The Date object for the specified Calendar cell
+ */
+ getDateByCellId : function(id) {
+ var date = this.getDateFieldsByCellId(id);
+ return YAHOO.widget.DateMath.getDate(date[0],date[1]-1,date[2]);
+ },
+
+ /**
+ * Retrieves the Date object for the specified Calendar cell
+ * @method getDateFieldsByCellId
+ * @param {String} id The id of the cell
+ * @return {Array} The array of Date fields for the specified Calendar cell
+ */
+ getDateFieldsByCellId : function(id) {
+ id = id.toLowerCase().split("_cell")[1];
+ id = parseInt(id, 10);
+ return this.cellDates[id];
+ },
+
+ /**
+ * Find the Calendar's cell index for a given date.
+ * If the date is not found, the method returns -1.
+ *
+ * The returned index can be used to lookup the cell HTMLElement
+ * using the Calendar's cells array or passed to selectCell to select
+ * cells by index.
+ *
+ *
+ * See cells, selectCell.
+ *
+ * @method getCellIndex
+ * @param {Date} date JavaScript Date object, for which to find a cell index.
+ * @return {Number} The index of the date in Calendars cellDates/cells arrays, or -1 if the date
+ * is not on the curently rendered Calendar page.
+ */
+ getCellIndex : function(date) {
+ var idx = -1;
+ if (date) {
+ var m = date.getMonth(),
+ y = date.getFullYear(),
+ d = date.getDate(),
+ dates = this.cellDates;
+
+ for (var i = 0; i < dates.length; ++i) {
+ var cellDate = dates[i];
+ if (cellDate[0] === y && cellDate[1] === m+1 && cellDate[2] === d) {
+ idx = i;
+ break;
+ }
+ }
+ }
+ return idx;
+ },
+
+ // BEGIN BUILT-IN TABLE CELL RENDERERS
+
+ /**
+ * Renders a cell that falls before the minimum date or after the maximum date.
+ * widget class.
+ * @method renderOutOfBoundsDate
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
+ * should not be terminated
+ */
+ renderOutOfBoundsDate : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_OOB);
+ cell.innerHTML = workingDate.getDate();
+ return YAHOO.widget.Calendar.STOP_RENDER;
+ },
+
+ /**
+ * Renders the row header for a week.
+ * @method renderRowHeader
+ * @param {Number} weekNum The week number of the current row
+ * @param {Array} cell The current working HTML array
+ */
+ renderRowHeader : function(weekNum, html) {
+ html[html.length] = '
' + weekNum + '
';
+ return html;
+ },
+
+ /**
+ * Renders the row footer for a week.
+ * @method renderRowFooter
+ * @param {Number} weekNum The week number of the current row
+ * @param {Array} cell The current working HTML array
+ */
+ renderRowFooter : function(weekNum, html) {
+ html[html.length] = '
' + weekNum + '
';
+ return html;
+ },
+
+ /**
+ * Renders a single standard calendar cell in the calendar widget table.
+ * All logic for determining how a standard default cell will be rendered is
+ * encapsulated in this method, and must be accounted for when extending the
+ * widget class.
+ * @method renderCellDefault
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ */
+ renderCellDefault : function(workingDate, cell) {
+ cell.innerHTML = '' + this.buildDayLabel(workingDate) + "";
+ },
+
+ /**
+ * Styles a selectable cell.
+ * @method styleCellDefault
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ */
+ styleCellDefault : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_SELECTABLE);
+ },
+
+
+ /**
+ * Renders a single standard calendar cell using the CSS hightlight1 style
+ * @method renderCellStyleHighlight1
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ */
+ renderCellStyleHighlight1 : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT1);
+ },
+
+ /**
+ * Renders a single standard calendar cell using the CSS hightlight2 style
+ * @method renderCellStyleHighlight2
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ */
+ renderCellStyleHighlight2 : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT2);
+ },
+
+ /**
+ * Renders a single standard calendar cell using the CSS hightlight3 style
+ * @method renderCellStyleHighlight3
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ */
+ renderCellStyleHighlight3 : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT3);
+ },
+
+ /**
+ * Renders a single standard calendar cell using the CSS hightlight4 style
+ * @method renderCellStyleHighlight4
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ */
+ renderCellStyleHighlight4 : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT4);
+ },
+
+ /**
+ * Applies the default style used for rendering today's date to the current calendar cell
+ * @method renderCellStyleToday
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ */
+ renderCellStyleToday : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_TODAY);
+ },
+
+ /**
+ * Applies the default style used for rendering selected dates to the current calendar cell
+ * @method renderCellStyleSelected
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
+ * should not be terminated
+ */
+ renderCellStyleSelected : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_SELECTED);
+ },
+
+ /**
+ * Applies the default style used for rendering dates that are not a part of the current
+ * month (preceding or trailing the cells for the current month)
+ * @method renderCellNotThisMonth
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
+ * should not be terminated
+ */
+ renderCellNotThisMonth : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_OOM);
+ cell.innerHTML=workingDate.getDate();
+ return YAHOO.widget.Calendar.STOP_RENDER;
+ },
+
+ /**
+ * Renders the current calendar cell as a non-selectable "black-out" date using the default
+ * restricted style.
+ * @method renderBodyCellRestricted
+ * @param {Date} workingDate The current working Date object being used to generate the calendar
+ * @param {HTMLTableCellElement} cell The current working cell in the calendar
+ * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
+ * should not be terminated
+ */
+ renderBodyCellRestricted : function(workingDate, cell) {
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL);
+ YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_RESTRICTED);
+ cell.innerHTML=workingDate.getDate();
+ return YAHOO.widget.Calendar.STOP_RENDER;
+ },
+
+ // END BUILT-IN TABLE CELL RENDERERS
+
+ // BEGIN MONTH NAVIGATION METHODS
+
+ /**
+ * Adds the designated number of months to the current calendar month, and sets the current
+ * calendar page date to the new month.
+ * @method addMonths
+ * @param {Number} count The number of months to add to the current calendar
+ */
+ addMonths : function(count) {
+ var cfgPageDate = YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;
+ this.cfg.setProperty(cfgPageDate, YAHOO.widget.DateMath.add(this.cfg.getProperty(cfgPageDate), YAHOO.widget.DateMath.MONTH, count));
+ this.resetRenderers();
+ this.changePageEvent.fire();
+ },
+
+ /**
+ * Subtracts the designated number of months from the current calendar month, and sets the current
+ * calendar page date to the new month.
+ * @method subtractMonths
+ * @param {Number} count The number of months to subtract from the current calendar
+ */
+ subtractMonths : function(count) {
+ var cfgPageDate = YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;
+ this.cfg.setProperty(cfgPageDate, YAHOO.widget.DateMath.subtract(this.cfg.getProperty(cfgPageDate), YAHOO.widget.DateMath.MONTH, count));
+ this.resetRenderers();
+ this.changePageEvent.fire();
+ },
+
+ /**
+ * Adds the designated number of years to the current calendar, and sets the current
+ * calendar page date to the new month.
+ * @method addYears
+ * @param {Number} count The number of years to add to the current calendar
+ */
+ addYears : function(count) {
+ var cfgPageDate = YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;
+ this.cfg.setProperty(cfgPageDate, YAHOO.widget.DateMath.add(this.cfg.getProperty(cfgPageDate), YAHOO.widget.DateMath.YEAR, count));
+ this.resetRenderers();
+ this.changePageEvent.fire();
+ },
+
+ /**
+ * Subtcats the designated number of years from the current calendar, and sets the current
+ * calendar page date to the new month.
+ * @method subtractYears
+ * @param {Number} count The number of years to subtract from the current calendar
+ */
+ subtractYears : function(count) {
+ var cfgPageDate = YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key;
+ this.cfg.setProperty(cfgPageDate, YAHOO.widget.DateMath.subtract(this.cfg.getProperty(cfgPageDate), YAHOO.widget.DateMath.YEAR, count));
+ this.resetRenderers();
+ this.changePageEvent.fire();
+ },
+
+ /**
+ * Navigates to the next month page in the calendar widget.
+ * @method nextMonth
+ */
+ nextMonth : function() {
+ this.addMonths(1);
+ },
+
+ /**
+ * Navigates to the previous month page in the calendar widget.
+ * @method previousMonth
+ */
+ previousMonth : function() {
+ this.subtractMonths(1);
+ },
+
+ /**
+ * Navigates to the next year in the currently selected month in the calendar widget.
+ * @method nextYear
+ */
+ nextYear : function() {
+ this.addYears(1);
+ },
+
+ /**
+ * Navigates to the previous year in the currently selected month in the calendar widget.
+ * @method previousYear
+ */
+ previousYear : function() {
+ this.subtractYears(1);
+ },
+
+ // END MONTH NAVIGATION METHODS
+
+ // BEGIN SELECTION METHODS
+
+ /**
+ * Resets the calendar widget to the originally selected month and year, and
+ * sets the calendar to the initial selection(s).
+ * @method reset
+ */
+ reset : function() {
+ var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG;
+ this.cfg.resetProperty(defCfg.SELECTED.key);
+ this.cfg.resetProperty(defCfg.PAGEDATE.key);
+ this.resetEvent.fire();
+ },
+
+ /**
+ * Clears the selected dates in the current calendar widget and sets the calendar
+ * to the current month and year.
+ * @method clear
+ */
+ clear : function() {
+ var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG;
+ this.cfg.setProperty(defCfg.SELECTED.key, []);
+ this.cfg.setProperty(defCfg.PAGEDATE.key, new Date(this.today.getTime()));
+ this.clearEvent.fire();
+ },
+
+ /**
+ * Selects a date or a collection of dates on the current calendar. This method, by default,
+ * does not call the render method explicitly. Once selection has completed, render must be
+ * called for the changes to be reflected visually.
+ *
+ * Any dates which are OOB (out of bounds, not selectable) will not be selected and the array of
+ * selected dates passed to the selectEvent will not contain OOB dates.
+ *
+ * If all dates are OOB, the no state change will occur; beforeSelect and select events will not be fired.
+ *
+ * @method select
+ * @param {String/Date/Date[]} date The date string of dates to select in the current calendar. Valid formats are
+ * individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
+ * Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
+ * This method can also take a JavaScript Date object or an array of Date objects.
+ * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
+ */
+ select : function(date) {
+
+ var aToBeSelected = this._toFieldArray(date);
+
+ // Filtered array of valid dates
+ var validDates = [];
+ var selected = [];
+ var cfgSelected = YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key;
+
+ for (var a=0; a < aToBeSelected.length; ++a) {
+ var toSelect = aToBeSelected[a];
+
+ if (!this.isDateOOB(this._toDate(toSelect))) {
+
+ if (validDates.length === 0) {
+ this.beforeSelectEvent.fire();
+ selected = this.cfg.getProperty(cfgSelected);
+ }
+
+ validDates.push(toSelect);
+
+ if (this._indexOfSelectedFieldArray(toSelect) == -1) {
+ selected[selected.length] = toSelect;
+ }
+ }
+ }
+
+
+ if (validDates.length > 0) {
+ if (this.parent) {
+ this.parent.cfg.setProperty(cfgSelected, selected);
+ } else {
+ this.cfg.setProperty(cfgSelected, selected);
+ }
+ this.selectEvent.fire(validDates);
+ }
+
+ return this.getSelectedDates();
+ },
+
+ /**
+ * Selects a date on the current calendar by referencing the index of the cell that should be selected.
+ * This method is used to easily select a single cell (usually with a mouse click) without having to do
+ * a full render. The selected style is applied to the cell directly.
+ *
+ * If the cell is not marked with the CSS_CELL_SELECTABLE class (as is the case by default for out of month
+ * or out of bounds cells), it will not be selected and in such a case beforeSelect and select events will not be fired.
+ *
+ * @method selectCell
+ * @param {Number} cellIndex The index of the cell to select in the current calendar.
+ * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
+ */
+ selectCell : function(cellIndex) {
+
+ var cell = this.cells[cellIndex];
+ var cellDate = this.cellDates[cellIndex];
+ var dCellDate = this._toDate(cellDate);
+
+ var selectable = YAHOO.util.Dom.hasClass(cell, this.Style.CSS_CELL_SELECTABLE);
+
+ if (selectable) {
+
+ this.beforeSelectEvent.fire();
+
+ var cfgSelected = YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key;
+ var selected = this.cfg.getProperty(cfgSelected);
+
+ var selectDate = cellDate.concat();
+
+ if (this._indexOfSelectedFieldArray(selectDate) == -1) {
+ selected[selected.length] = selectDate;
+ }
+ if (this.parent) {
+ this.parent.cfg.setProperty(cfgSelected, selected);
+ } else {
+ this.cfg.setProperty(cfgSelected, selected);
+ }
+ this.renderCellStyleSelected(dCellDate,cell);
+ this.selectEvent.fire([selectDate]);
+
+ this.doCellMouseOut.call(cell, null, this);
+ }
+
+ return this.getSelectedDates();
+ },
+
+ /**
+ * Deselects a date or a collection of dates on the current calendar. This method, by default,
+ * does not call the render method explicitly. Once deselection has completed, render must be
+ * called for the changes to be reflected visually.
+ *
+ * The method will not attempt to deselect any dates which are OOB (out of bounds, and hence not selectable)
+ * and the array of deselected dates passed to the deselectEvent will not contain any OOB dates.
+ *
+ * If all dates are OOB, beforeDeselect and deselect events will not be fired.
+ *
+ * @method deselect
+ * @param {String/Date/Date[]} date The date string of dates to deselect in the current calendar. Valid formats are
+ * individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
+ * Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
+ * This method can also take a JavaScript Date object or an array of Date objects.
+ * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
+ */
+ deselect : function(date) {
+
+ var aToBeDeselected = this._toFieldArray(date);
+
+ var validDates = [];
+ var selected = [];
+ var cfgSelected = YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key;
+
+ for (var a=0; a < aToBeDeselected.length; ++a) {
+ var toDeselect = aToBeDeselected[a];
+
+ if (!this.isDateOOB(this._toDate(toDeselect))) {
+
+ if (validDates.length === 0) {
+ this.beforeDeselectEvent.fire();
+ selected = this.cfg.getProperty(cfgSelected);
+ }
+
+ validDates.push(toDeselect);
+
+ var index = this._indexOfSelectedFieldArray(toDeselect);
+ if (index != -1) {
+ selected.splice(index,1);
+ }
+ }
+ }
+
+
+ if (validDates.length > 0) {
+ if (this.parent) {
+ this.parent.cfg.setProperty(cfgSelected, selected);
+ } else {
+ this.cfg.setProperty(cfgSelected, selected);
+ }
+ this.deselectEvent.fire(validDates);
+ }
+
+ return this.getSelectedDates();
+ },
+
+ /**
+ * Deselects a date on the current calendar by referencing the index of the cell that should be deselected.
+ * This method is used to easily deselect a single cell (usually with a mouse click) without having to do
+ * a full render. The selected style is removed from the cell directly.
+ *
+ * If the cell is not marked with the CSS_CELL_SELECTABLE class (as is the case by default for out of month
+ * or out of bounds cells), the method will not attempt to deselect it and in such a case, beforeDeselect and
+ * deselect events will not be fired.
+ *
+ * @method deselectCell
+ * @param {Number} cellIndex The index of the cell to deselect in the current calendar.
+ * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
+ */
+ deselectCell : function(cellIndex) {
+ var cell = this.cells[cellIndex];
+ var cellDate = this.cellDates[cellIndex];
+ var cellDateIndex = this._indexOfSelectedFieldArray(cellDate);
+
+ var selectable = YAHOO.util.Dom.hasClass(cell, this.Style.CSS_CELL_SELECTABLE);
+
+ if (selectable) {
+
+ this.beforeDeselectEvent.fire();
+
+ var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG;
+ var selected = this.cfg.getProperty(defCfg.SELECTED.key);
+
+ var dCellDate = this._toDate(cellDate);
+ var selectDate = cellDate.concat();
+
+ if (cellDateIndex > -1) {
+ if (this.cfg.getProperty(defCfg.PAGEDATE.key).getMonth() == dCellDate.getMonth() &&
+ this.cfg.getProperty(defCfg.PAGEDATE.key).getFullYear() == dCellDate.getFullYear()) {
+ YAHOO.util.Dom.removeClass(cell, this.Style.CSS_CELL_SELECTED);
+ }
+ selected.splice(cellDateIndex, 1);
+ }
+
+ if (this.parent) {
+ this.parent.cfg.setProperty(defCfg.SELECTED.key, selected);
+ } else {
+ this.cfg.setProperty(defCfg.SELECTED.key, selected);
+ }
+
+ this.deselectEvent.fire(selectDate);
+ }
+
+ return this.getSelectedDates();
+ },
+
+ /**
+ * Deselects all dates on the current calendar.
+ * @method deselectAll
+ * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
+ * Assuming that this function executes properly, the return value should be an empty array.
+ * However, the empty array is returned for the sake of being able to check the selection status
+ * of the calendar.
+ */
+ deselectAll : function() {
+ this.beforeDeselectEvent.fire();
+
+ var cfgSelected = YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key;
+
+ var selected = this.cfg.getProperty(cfgSelected);
+ var count = selected.length;
+ var sel = selected.concat();
+
+ if (this.parent) {
+ this.parent.cfg.setProperty(cfgSelected, []);
+ } else {
+ this.cfg.setProperty(cfgSelected, []);
+ }
+
+ if (count > 0) {
+ this.deselectEvent.fire(sel);
+ }
+
+ return this.getSelectedDates();
+ },
+
+ // END SELECTION METHODS
+
+ // BEGIN TYPE CONVERSION METHODS
+
+ /**
+ * Converts a date (either a JavaScript Date object, or a date string) to the internal data structure
+ * used to represent dates: [[yyyy,mm,dd],[yyyy,mm,dd]].
+ * @method _toFieldArray
+ * @private
+ * @param {String/Date/Date[]} date The date string of dates to deselect in the current calendar. Valid formats are
+ * individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
+ * Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
+ * This method can also take a JavaScript Date object or an array of Date objects.
+ * @return {Array[](Number[])} Array of date field arrays
+ */
+ _toFieldArray : function(date) {
+ var returnDate = [];
+
+ if (date instanceof Date) {
+ returnDate = [[date.getFullYear(), date.getMonth()+1, date.getDate()]];
+ } else if (YAHOO.lang.isString(date)) {
+ returnDate = this._parseDates(date);
+ } else if (YAHOO.lang.isArray(date)) {
+ for (var i=0;i maxDate.getTime()));
+ },
+
+ /**
+ * Parses a pagedate configuration property value. The value can either be specified as a string of form "mm/yyyy" or a Date object
+ * and is parsed into a Date object normalized to the first day of the month. If no value is passed in, the month and year from today's date are used to create the Date object
+ * @method _parsePageDate
+ * @private
+ * @param {Date|String} date Pagedate value which needs to be parsed
+ * @return {Date} The Date object representing the pagedate
+ */
+ _parsePageDate : function(date) {
+ var parsedDate;
+
+ var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG;
+
+ if (date) {
+ if (date instanceof Date) {
+ parsedDate = YAHOO.widget.DateMath.findMonthStart(date);
+ } else {
+ var month, year, aMonthYear;
+ aMonthYear = date.split(this.cfg.getProperty(defCfg.DATE_FIELD_DELIMITER.key));
+ month = parseInt(aMonthYear[this.cfg.getProperty(defCfg.MY_MONTH_POSITION.key)-1], 10)-1;
+ year = parseInt(aMonthYear[this.cfg.getProperty(defCfg.MY_YEAR_POSITION.key)-1], 10);
+
+ parsedDate = YAHOO.widget.DateMath.getDate(year, month, 1);
+ }
+ } else {
+ parsedDate = YAHOO.widget.DateMath.getDate(this.today.getFullYear(), this.today.getMonth(), 1);
+ }
+ return parsedDate;
+ },
+
+ // END UTILITY METHODS
+
+ // BEGIN EVENT HANDLERS
+
+ /**
+ * Event executed before a date is selected in the calendar widget.
+ * @deprecated Event handlers for this event should be susbcribed to beforeSelectEvent.
+ */
+ onBeforeSelect : function() {
+ if (this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.MULTI_SELECT.key) === false) {
+ if (this.parent) {
+ this.parent.callChildFunction("clearAllBodyCellStyles", this.Style.CSS_CELL_SELECTED);
+ this.parent.deselectAll();
+ } else {
+ this.clearAllBodyCellStyles(this.Style.CSS_CELL_SELECTED);
+ this.deselectAll();
+ }
+ }
+ },
+
+ /**
+ * Event executed when a date is selected in the calendar widget.
+ * @param {Array} selected An array of date field arrays representing which date or dates were selected. Example: [ [2006,8,6],[2006,8,7],[2006,8,8] ]
+ * @deprecated Event handlers for this event should be susbcribed to selectEvent.
+ */
+ onSelect : function(selected) { },
+
+ /**
+ * Event executed before a date is deselected in the calendar widget.
+ * @deprecated Event handlers for this event should be susbcribed to beforeDeselectEvent.
+ */
+ onBeforeDeselect : function() { },
+
+ /**
+ * Event executed when a date is deselected in the calendar widget.
+ * @param {Array} selected An array of date field arrays representing which date or dates were deselected. Example: [ [2006,8,6],[2006,8,7],[2006,8,8] ]
+ * @deprecated Event handlers for this event should be susbcribed to deselectEvent.
+ */
+ onDeselect : function(deselected) { },
+
+ /**
+ * Event executed when the user navigates to a different calendar page.
+ * @deprecated Event handlers for this event should be susbcribed to changePageEvent.
+ */
+ onChangePage : function() {
+ this.render();
+ },
+
+ /**
+ * Event executed when the calendar widget is rendered.
+ * @deprecated Event handlers for this event should be susbcribed to renderEvent.
+ */
+ onRender : function() { },
+
+ /**
+ * Event executed when the calendar widget is reset to its original state.
+ * @deprecated Event handlers for this event should be susbcribed to resetEvemt.
+ */
+ onReset : function() { this.render(); },
+
+ /**
+ * Event executed when the calendar widget is completely cleared to the current month with no selections.
+ * @deprecated Event handlers for this event should be susbcribed to clearEvent.
+ */
+ onClear : function() { this.render(); },
+
+ /**
+ * Validates the calendar widget. This method has no default implementation
+ * and must be extended by subclassing the widget.
+ * @return Should return true if the widget validates, and false if
+ * it doesn't.
+ * @type Boolean
+ */
+ validate : function() { return true; },
+
+ // END EVENT HANDLERS
+
+ // BEGIN DATE PARSE METHODS
+
+ /**
+ * Converts a date string to a date field array
+ * @private
+ * @param {String} sDate Date string. Valid formats are mm/dd and mm/dd/yyyy.
+ * @return A date field array representing the string passed to the method
+ * @type Array[](Number[])
+ */
+ _parseDate : function(sDate) {
+ var aDate = sDate.split(this.Locale.DATE_FIELD_DELIMITER);
+ var rArray;
+
+ if (aDate.length == 2) {
+ rArray = [aDate[this.Locale.MD_MONTH_POSITION-1],aDate[this.Locale.MD_DAY_POSITION-1]];
+ rArray.type = YAHOO.widget.Calendar.MONTH_DAY;
+ } else {
+ rArray = [aDate[this.Locale.MDY_YEAR_POSITION-1],aDate[this.Locale.MDY_MONTH_POSITION-1],aDate[this.Locale.MDY_DAY_POSITION-1]];
+ rArray.type = YAHOO.widget.Calendar.DATE;
+ }
+
+ for (var i=0;i
+*
+*
+*
+* The tables for the calendars ("cal1_0" and "cal1_1") will be inserted into those containers.
+*
+*
+* NOTE: As of 2.4.0, the constructor's ID argument is optional.
+* The CalendarGroup can be constructed by simply providing a container ID string,
+* or a reference to a container DIV HTMLElement (the element needs to exist
+* in the document).
+*
+* E.g.:
+*
+* var c = new YAHOO.widget.CalendarGroup("calContainer", configOptions);
+*
+* or:
+*
+* var containerDiv = YAHOO.util.Dom.get("calContainer");
+* var c = new YAHOO.widget.CalendarGroup(containerDiv, configOptions);
+*
+*
+*
+* If not provided, the ID will be generated from the container DIV ID by adding an "_t" suffix.
+* For example if an ID is not provided, and the container's ID is "calContainer", the CalendarGroup's ID will be set to "calContainer_t".
+*
+*
+* @namespace YAHOO.widget
+* @class CalendarGroup
+* @constructor
+* @param {String} id optional The id of the table element that will represent the CalendarGroup widget. As of 2.4.0, this argument is optional.
+* @param {String | HTMLElement} container The id of the container div element that will wrap the CalendarGroup table, or a reference to a DIV element which exists in the document.
+* @param {Object} config optional The configuration object containing the initial configuration values for the CalendarGroup.
+*/
+YAHOO.widget.CalendarGroup = function(id, containerId, config) {
+ if (arguments.length > 0) {
+ this.init.apply(this, arguments);
+ }
+};
+
+YAHOO.widget.CalendarGroup.prototype = {
+
+ /**
+ * Initializes the calendar group. All subclasses must call this method in order for the
+ * group to be initialized properly.
+ * @method init
+ * @param {String} id optional The id of the table element that will represent the CalendarGroup widget. As of 2.4.0, this argument is optional.
+ * @param {String | HTMLElement} container The id of the container div element that will wrap the CalendarGroup table, or a reference to a DIV element which exists in the document.
+ * @param {Object} config optional The configuration object containing the initial configuration values for the CalendarGroup.
+ */
+ init : function(id, container, config) {
+
+ // Normalize 2.4.0, pre 2.4.0 args
+ var nArgs = this._parseArgs(arguments);
+
+ id = nArgs.id;
+ container = nArgs.container;
+ config = nArgs.config;
+
+ this.oDomContainer = YAHOO.util.Dom.get(container);
+
+ if (!this.oDomContainer.id) {
+ this.oDomContainer.id = YAHOO.util.Dom.generateId();
+ }
+ if (!id) {
+ id = this.oDomContainer.id + "_t";
+ }
+
+ /**
+ * The unique id associated with the CalendarGroup
+ * @property id
+ * @type String
+ */
+ this.id = id;
+
+ /**
+ * The unique id associated with the CalendarGroup container
+ * @property containerId
+ * @type String
+ */
+ this.containerId = this.oDomContainer.id;
+
+ this.initEvents();
+ this.initStyles();
+
+ /**
+ * The collection of Calendar pages contained within the CalendarGroup
+ * @property pages
+ * @type YAHOO.widget.Calendar[]
+ */
+ this.pages = [];
+
+ YAHOO.util.Dom.addClass(this.oDomContainer, YAHOO.widget.CalendarGroup.CSS_CONTAINER);
+ YAHOO.util.Dom.addClass(this.oDomContainer, YAHOO.widget.CalendarGroup.CSS_MULTI_UP);
+
+ /**
+ * The Config object used to hold the configuration variables for the CalendarGroup
+ * @property cfg
+ * @type YAHOO.util.Config
+ */
+ this.cfg = new YAHOO.util.Config(this);
+
+ /**
+ * The local object which contains the CalendarGroup's options
+ * @property Options
+ * @type Object
+ */
+ this.Options = {};
+
+ /**
+ * The local object which contains the CalendarGroup's locale settings
+ * @property Locale
+ * @type Object
+ */
+ this.Locale = {};
+
+ this.setupConfig();
+
+ if (config) {
+ this.cfg.applyConfig(config, true);
+ }
+
+ this.cfg.fireQueue();
+
+ // OPERA HACK FOR MISWRAPPED FLOATS
+ if (YAHOO.env.ua.opera){
+ this.renderEvent.subscribe(this._fixWidth, this, true);
+ this.showEvent.subscribe(this._fixWidth, this, true);
+ }
+
+ },
+
+ setupConfig : function() {
+
+ var defCfg = YAHOO.widget.CalendarGroup._DEFAULT_CONFIG;
+
+ /**
+ * The number of pages to include in the CalendarGroup. This value can only be set once, in the CalendarGroup's constructor arguments.
+ * @config pages
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.PAGES.key, { value:defCfg.PAGES.value, validator:this.cfg.checkNumber, handler:this.configPages } );
+
+ /**
+ * The month/year representing the current visible Calendar date (mm/yyyy)
+ * @config pagedate
+ * @type String
+ * @default today's date
+ */
+ this.cfg.addProperty(defCfg.PAGEDATE.key, { value:new Date(), handler:this.configPageDate } );
+
+ /**
+ * The date or range of dates representing the current Calendar selection
+ * @config selected
+ * @type String
+ * @default []
+ */
+ this.cfg.addProperty(defCfg.SELECTED.key, { value:[], handler:this.configSelected } );
+
+ /**
+ * The title to display above the CalendarGroup's month header
+ * @config title
+ * @type String
+ * @default ""
+ */
+ this.cfg.addProperty(defCfg.TITLE.key, { value:defCfg.TITLE.value, handler:this.configTitle } );
+
+ /**
+ * Whether or not a close button should be displayed for this CalendarGroup
+ * @config close
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.CLOSE.key, { value:defCfg.CLOSE.value, handler:this.configClose } );
+
+ /**
+ * Whether or not an iframe shim should be placed under the Calendar to prevent select boxes from bleeding through in Internet Explorer 6 and below.
+ * This property is enabled by default for IE6 and below. It is disabled by default for other browsers for performance reasons, but can be
+ * enabled if required.
+ *
+ * @config iframe
+ * @type Boolean
+ * @default true for IE6 and below, false for all other browsers
+ */
+ this.cfg.addProperty(defCfg.IFRAME.key, { value:defCfg.IFRAME.value, handler:this.configIframe, validator:this.cfg.checkBoolean } );
+
+ /**
+ * The minimum selectable date in the current Calendar (mm/dd/yyyy)
+ * @config mindate
+ * @type String
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.MINDATE.key, { value:defCfg.MINDATE.value, handler:this.delegateConfig } );
+
+ /**
+ * The maximum selectable date in the current Calendar (mm/dd/yyyy)
+ * @config maxdate
+ * @type String
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.MAXDATE.key, { value:defCfg.MAXDATE.value, handler:this.delegateConfig } );
+
+ // Options properties
+
+ /**
+ * True if the Calendar should allow multiple selections. False by default.
+ * @config MULTI_SELECT
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.MULTI_SELECT.key, { value:defCfg.MULTI_SELECT.value, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+ /**
+ * The weekday the week begins on. Default is 0 (Sunday).
+ * @config START_WEEKDAY
+ * @type number
+ * @default 0
+ */
+ this.cfg.addProperty(defCfg.START_WEEKDAY.key, { value:defCfg.START_WEEKDAY.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * True if the Calendar should show weekday labels. True by default.
+ * @config SHOW_WEEKDAYS
+ * @type Boolean
+ * @default true
+ */
+ this.cfg.addProperty(defCfg.SHOW_WEEKDAYS.key, { value:defCfg.SHOW_WEEKDAYS.value, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+ /**
+ * True if the Calendar should show week row headers. False by default.
+ * @config SHOW_WEEK_HEADER
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.SHOW_WEEK_HEADER.key,{ value:defCfg.SHOW_WEEK_HEADER.value, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+ /**
+ * True if the Calendar should show week row footers. False by default.
+ * @config SHOW_WEEK_FOOTER
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.SHOW_WEEK_FOOTER.key,{ value:defCfg.SHOW_WEEK_FOOTER.value, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+ /**
+ * True if the Calendar should suppress weeks that are not a part of the current month. False by default.
+ * @config HIDE_BLANK_WEEKS
+ * @type Boolean
+ * @default false
+ */
+ this.cfg.addProperty(defCfg.HIDE_BLANK_WEEKS.key,{ value:defCfg.HIDE_BLANK_WEEKS.value, handler:this.delegateConfig, validator:this.cfg.checkBoolean } );
+
+ /**
+ * The image that should be used for the left navigation arrow.
+ * @config NAV_ARROW_LEFT
+ * @type String
+ * @deprecated You can customize the image by overriding the default CSS class for the left arrow - "calnavleft"
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.NAV_ARROW_LEFT.key, { value:defCfg.NAV_ARROW_LEFT.value, handler:this.delegateConfig } );
+
+ /**
+ * The image that should be used for the right navigation arrow.
+ * @config NAV_ARROW_RIGHT
+ * @type String
+ * @deprecated You can customize the image by overriding the default CSS class for the right arrow - "calnavright"
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.NAV_ARROW_RIGHT.key, { value:defCfg.NAV_ARROW_RIGHT.value, handler:this.delegateConfig } );
+
+ // Locale properties
+
+ /**
+ * The short month labels for the current locale.
+ * @config MONTHS_SHORT
+ * @type String[]
+ * @default ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+ */
+ this.cfg.addProperty(defCfg.MONTHS_SHORT.key, { value:defCfg.MONTHS_SHORT.value, handler:this.delegateConfig } );
+
+ /**
+ * The long month labels for the current locale.
+ * @config MONTHS_LONG
+ * @type String[]
+ * @default ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
+ */
+ this.cfg.addProperty(defCfg.MONTHS_LONG.key, { value:defCfg.MONTHS_LONG.value, handler:this.delegateConfig } );
+
+ /**
+ * The 1-character weekday labels for the current locale.
+ * @config WEEKDAYS_1CHAR
+ * @type String[]
+ * @default ["S", "M", "T", "W", "T", "F", "S"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_1CHAR.key, { value:defCfg.WEEKDAYS_1CHAR.value, handler:this.delegateConfig } );
+
+ /**
+ * The short weekday labels for the current locale.
+ * @config WEEKDAYS_SHORT
+ * @type String[]
+ * @default ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_SHORT.key, { value:defCfg.WEEKDAYS_SHORT.value, handler:this.delegateConfig } );
+
+ /**
+ * The medium weekday labels for the current locale.
+ * @config WEEKDAYS_MEDIUM
+ * @type String[]
+ * @default ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_MEDIUM.key, { value:defCfg.WEEKDAYS_MEDIUM.value, handler:this.delegateConfig } );
+
+ /**
+ * The long weekday labels for the current locale.
+ * @config WEEKDAYS_LONG
+ * @type String[]
+ * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
+ */
+ this.cfg.addProperty(defCfg.WEEKDAYS_LONG.key, { value:defCfg.WEEKDAYS_LONG.value, handler:this.delegateConfig } );
+
+ /**
+ * The setting that determines which length of month labels should be used. Possible values are "short" and "long".
+ * @config LOCALE_MONTHS
+ * @type String
+ * @default "long"
+ */
+ this.cfg.addProperty(defCfg.LOCALE_MONTHS.key, { value:defCfg.LOCALE_MONTHS.value, handler:this.delegateConfig } );
+
+ /**
+ * The setting that determines which length of weekday labels should be used. Possible values are "1char", "short", "medium", and "long".
+ * @config LOCALE_WEEKDAYS
+ * @type String
+ * @default "short"
+ */
+ this.cfg.addProperty(defCfg.LOCALE_WEEKDAYS.key, { value:defCfg.LOCALE_WEEKDAYS.value, handler:this.delegateConfig } );
+
+ /**
+ * The value used to delimit individual dates in a date string passed to various Calendar functions.
+ * @config DATE_DELIMITER
+ * @type String
+ * @default ","
+ */
+ this.cfg.addProperty(defCfg.DATE_DELIMITER.key, { value:defCfg.DATE_DELIMITER.value, handler:this.delegateConfig } );
+
+ /**
+ * The value used to delimit date fields in a date string passed to various Calendar functions.
+ * @config DATE_FIELD_DELIMITER
+ * @type String
+ * @default "/"
+ */
+ this.cfg.addProperty(defCfg.DATE_FIELD_DELIMITER.key,{ value:defCfg.DATE_FIELD_DELIMITER.value, handler:this.delegateConfig } );
+
+ /**
+ * The value used to delimit date ranges in a date string passed to various Calendar functions.
+ * @config DATE_RANGE_DELIMITER
+ * @type String
+ * @default "-"
+ */
+ this.cfg.addProperty(defCfg.DATE_RANGE_DELIMITER.key,{ value:defCfg.DATE_RANGE_DELIMITER.value, handler:this.delegateConfig } );
+
+ /**
+ * The position of the month in a month/year date string
+ * @config MY_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MY_MONTH_POSITION.key, { value:defCfg.MY_MONTH_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the year in a month/year date string
+ * @config MY_YEAR_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MY_YEAR_POSITION.key, { value:defCfg.MY_YEAR_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the month in a month/day date string
+ * @config MD_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MD_MONTH_POSITION.key, { value:defCfg.MD_MONTH_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the day in a month/year date string
+ * @config MD_DAY_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MD_DAY_POSITION.key, { value:defCfg.MD_DAY_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the month in a month/day/year date string
+ * @config MDY_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MDY_MONTH_POSITION.key, { value:defCfg.MDY_MONTH_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the day in a month/day/year date string
+ * @config MDY_DAY_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MDY_DAY_POSITION.key, { value:defCfg.MDY_DAY_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the year in a month/day/year date string
+ * @config MDY_YEAR_POSITION
+ * @type Number
+ * @default 3
+ */
+ this.cfg.addProperty(defCfg.MDY_YEAR_POSITION.key, { value:defCfg.MDY_YEAR_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the month in the month year label string used as the Calendar header
+ * @config MY_LABEL_MONTH_POSITION
+ * @type Number
+ * @default 1
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_MONTH_POSITION.key, { value:defCfg.MY_LABEL_MONTH_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The position of the year in the month year label string used as the Calendar header
+ * @config MY_LABEL_YEAR_POSITION
+ * @type Number
+ * @default 2
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_YEAR_POSITION.key, { value:defCfg.MY_LABEL_YEAR_POSITION.value, handler:this.delegateConfig, validator:this.cfg.checkNumber } );
+
+ /**
+ * The suffix used after the month when rendering the Calendar header
+ * @config MY_LABEL_MONTH_SUFFIX
+ * @type String
+ * @default " "
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_MONTH_SUFFIX.key, { value:defCfg.MY_LABEL_MONTH_SUFFIX.value, handler:this.delegateConfig } );
+
+ /**
+ * The suffix used after the year when rendering the Calendar header
+ * @config MY_LABEL_YEAR_SUFFIX
+ * @type String
+ * @default ""
+ */
+ this.cfg.addProperty(defCfg.MY_LABEL_YEAR_SUFFIX.key, { value:defCfg.MY_LABEL_YEAR_SUFFIX.value, handler:this.delegateConfig } );
+
+ /**
+ * Configuration for the Month Year Navigation UI. By default it is disabled
+ * @config NAV
+ * @type Object
+ * @default null
+ */
+ this.cfg.addProperty(defCfg.NAV.key, { value:defCfg.NAV.value, handler:this.configNavigator } );
+ },
+
+ /**
+ * Initializes CalendarGroup's built-in CustomEvents
+ * @method initEvents
+ */
+ initEvents : function() {
+ var me = this;
+ var strEvent = "Event";
+
+ /**
+ * Proxy subscriber to subscribe to the CalendarGroup's child Calendars' CustomEvents
+ * @method sub
+ * @private
+ * @param {Function} fn The function to subscribe to this CustomEvent
+ * @param {Object} obj The CustomEvent's scope object
+ * @param {Boolean} bOverride Whether or not to apply scope correction
+ */
+ var sub = function(fn, obj, bOverride) {
+ for (var p=0;p 0) ? this.pages[0].cfg.getProperty(cfgSelected) : [];
+ this.cfg.setProperty(cfgSelected, selected, true);
+ },
+
+
+ /**
+ * Delegates a configuration property to the CustomEvents associated with the CalendarGroup's children
+ * @method delegateConfig
+ * @param {String} type The CustomEvent type (usually the property name)
+ * @param {Object[]} args The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
+ * @param {Object} obj The scope object. For configuration handlers, this will usually equal the owner.
+ */
+ delegateConfig : function(type, args, obj) {
+ var val = args[0];
+ var cal;
+
+ for (var p=0;p0) {
+ year+=1;
+ }
+ cal.setYear(year);
+ }
+ },
+
+ /**
+ * Calls the render function of all child calendars within the group.
+ * @method render
+ */
+ render : function() {
+ this.renderHeader();
+ for (var p=0;p
+ *
If MULTI_SELECT is false, selectCell will select the cell at the specified index for only the last displayed Calendar page.
+ *
If MULTI_SELECT is true, selectCell will select the cell at the specified index, on each displayed Calendar page.
+ *
+ * @method selectCell
+ * @param {Number} cellIndex The index of the cell to be selected.
+ * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
+ */
+ selectCell : function(cellIndex) {
+ for (var p=0;p=0;--p) {
+ var cal = this.pages[p];
+ cal.previousMonth();
+ }
+ },
+
+ /**
+ * Navigates to the next year in the currently selected month in the calendar widget.
+ * @method nextYear
+ */
+ nextYear : function() {
+ for (var p=0;p 11)) {
+ var DM = YAHOO.widget.DateMath;
+ var newDate = DM.add(date, DM.MONTH, iMonth-date.getMonth());
+ date.setTime(newDate.getTime());
+ } else {
+ date.setMonth(iMonth);
+ }
+ },
+
+ /**
+ * Fixes the width of the CalendarGroup container element, to account for miswrapped floats
+ * @method _fixWidth
+ * @private
+ */
+ _fixWidth : function() {
+ var w = 0;
+ for (var p=0;p 0) {
+ this.oDomContainer.style.width = w + "px";
+ }
+ },
+
+ /**
+ * Returns a string representation of the object.
+ * @method toString
+ * @return {String} A string representation of the CalendarGroup object.
+ */
+ toString : function() {
+ return "CalendarGroup " + this.id;
+ }
+};
+
+/**
+* CSS class representing the container for the calendar
+* @property YAHOO.widget.CalendarGroup.CSS_CONTAINER
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.CalendarGroup.CSS_CONTAINER = "yui-calcontainer";
+
+/**
+* CSS class representing the container for the calendar
+* @property YAHOO.widget.CalendarGroup.CSS_MULTI_UP
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.CalendarGroup.CSS_MULTI_UP = "multi";
+
+/**
+* CSS class representing the title for the 2-up calendar
+* @property YAHOO.widget.CalendarGroup.CSS_2UPTITLE
+* @static
+* @final
+* @type String
+*/
+YAHOO.widget.CalendarGroup.CSS_2UPTITLE = "title";
+
+/**
+* CSS class representing the close icon for the 2-up calendar
+* @property YAHOO.widget.CalendarGroup.CSS_2UPCLOSE
+* @static
+* @final
+* @deprecated Along with Calendar.IMG_ROOT and NAV_ARROW_LEFT, NAV_ARROW_RIGHT configuration properties.
+* Calendar's Style.CSS_CLOSE property now represents the CSS class used to render the close icon
+* @type String
+*/
+YAHOO.widget.CalendarGroup.CSS_2UPCLOSE = "close-icon";
+
+YAHOO.lang.augmentProto(YAHOO.widget.CalendarGroup, YAHOO.widget.Calendar, "buildDayLabel",
+ "buildMonthLabel",
+ "renderOutOfBoundsDate",
+ "renderRowHeader",
+ "renderRowFooter",
+ "renderCellDefault",
+ "styleCellDefault",
+ "renderCellStyleHighlight1",
+ "renderCellStyleHighlight2",
+ "renderCellStyleHighlight3",
+ "renderCellStyleHighlight4",
+ "renderCellStyleToday",
+ "renderCellStyleSelected",
+ "renderCellNotThisMonth",
+ "renderBodyCellRestricted",
+ "initStyles",
+ "configTitle",
+ "configClose",
+ "configIframe",
+ "configNavigator",
+ "createTitleBar",
+ "createCloseButton",
+ "removeTitleBar",
+ "removeCloseButton",
+ "hide",
+ "show",
+ "toDate",
+ "_toDate",
+ "_parseArgs",
+ "browser");
+
+/**
+* The set of default Config property keys and values for the CalendarGroup
+* @property YAHOO.widget.CalendarGroup._DEFAULT_CONFIG
+* @final
+* @static
+* @private
+* @type Object
+*/
+YAHOO.widget.CalendarGroup._DEFAULT_CONFIG = YAHOO.widget.Calendar._DEFAULT_CONFIG;
+YAHOO.widget.CalendarGroup._DEFAULT_CONFIG.PAGES = {key:"pages", value:2};
+
+YAHOO.widget.CalGrp = YAHOO.widget.CalendarGroup;
+
+/**
+* @class YAHOO.widget.Calendar2up
+* @extends YAHOO.widget.CalendarGroup
+* @deprecated The old Calendar2up class is no longer necessary, since CalendarGroup renders in a 2up view by default.
+*/
+YAHOO.widget.Calendar2up = function(id, containerId, config) {
+ this.init(id, containerId, config);
+};
+
+YAHOO.extend(YAHOO.widget.Calendar2up, YAHOO.widget.CalendarGroup);
+
+/**
+* @deprecated The old Calendar2up class is no longer necessary, since CalendarGroup renders in a 2up view by default.
+*/
+YAHOO.widget.Cal2up = YAHOO.widget.Calendar2up;
+
+/**
+ * The CalendarNavigator is used along with a Calendar/CalendarGroup to
+ * provide a Month/Year popup navigation control, allowing the user to navigate
+ * to a specific month/year in the Calendar/CalendarGroup without having to
+ * scroll through months sequentially
+ *
+ * @namespace YAHOO.widget
+ * @class CalendarNavigator
+ * @constructor
+ * @param {Calendar|CalendarGroup} cal The instance of the Calendar or CalendarGroup to which this CalendarNavigator should be attached.
+ */
+YAHOO.widget.CalendarNavigator = function(cal) {
+ this.init(cal);
+};
+
+(function() {
+ // Setup static properties (inside anon fn, so that we can use shortcuts)
+ var CN = YAHOO.widget.CalendarNavigator;
+
+ /**
+ * YAHOO.widget.CalendarNavigator.CLASSES contains constants
+ * for the class values applied to the CalendarNaviatgator's
+ * DOM elements
+ * @property YAHOO.widget.CalendarNavigator.CLASSES
+ * @type Object
+ * @static
+ */
+ CN.CLASSES = {
+ /**
+ * Class applied to the Calendar Navigator's bounding box
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.NAV
+ * @type String
+ * @static
+ */
+ NAV :"yui-cal-nav",
+ /**
+ * Class applied to the Calendar/CalendarGroup's bounding box to indicate
+ * the Navigator is currently visible
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.NAV_VISIBLE
+ * @type String
+ * @static
+ */
+ NAV_VISIBLE: "yui-cal-nav-visible",
+ /**
+ * Class applied to the Navigator mask's bounding box
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.MASK
+ * @type String
+ * @static
+ */
+ MASK : "yui-cal-nav-mask",
+ /**
+ * Class applied to the year label/control bounding box
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.YEAR
+ * @type String
+ * @static
+ */
+ YEAR : "yui-cal-nav-y",
+ /**
+ * Class applied to the month label/control bounding box
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.MONTH
+ * @type String
+ * @static
+ */
+ MONTH : "yui-cal-nav-m",
+ /**
+ * Class applied to the submit/cancel button's bounding box
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.BUTTONS
+ * @type String
+ * @static
+ */
+ BUTTONS : "yui-cal-nav-b",
+ /**
+ * Class applied to buttons wrapping element
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.BUTTON
+ * @type String
+ * @static
+ */
+ BUTTON : "yui-cal-nav-btn",
+ /**
+ * Class applied to the validation error area's bounding box
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.ERROR
+ * @type String
+ * @static
+ */
+ ERROR : "yui-cal-nav-e",
+ /**
+ * Class applied to the year input control
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.YEAR_CTRL
+ * @type String
+ * @static
+ */
+ YEAR_CTRL : "yui-cal-nav-yc",
+ /**
+ * Class applied to the month input control
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.MONTH_CTRL
+ * @type String
+ * @static
+ */
+ MONTH_CTRL : "yui-cal-nav-mc",
+ /**
+ * Class applied to controls with invalid data (e.g. a year input field with invalid an year)
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.INVALID
+ * @type String
+ * @static
+ */
+ INVALID : "yui-invalid",
+ /**
+ * Class applied to default controls
+ * @property YAHOO.widget.CalendarNavigator.CLASSES.DEFAULT
+ * @type String
+ * @static
+ */
+ DEFAULT : "yui-default"
+ };
+
+ /**
+ * Object literal containing the default configuration values for the CalendarNavigator
+ * The configuration object is expected to follow the format below, with the properties being
+ * case sensitive.
+ *
+ *
strings
+ *
Object : An object with the properties shown below, defining the string labels to use in the Navigator's UI
+ *
+ *
month
String : The string to use for the month label. Defaults to "Month".
+ *
year
String : The string to use for the year label. Defaults to "Year".
+ *
submit
String : The string to use for the submit button label. Defaults to "Okay".
+ *
cancel
String : The string to use for the cancel button label. Defaults to "Cancel".
+ *
invalidYear
String : The string to use for invalid year values. Defaults to "Year needs to be a number".
+ *
+ *
+ *
monthFormat
String : The month format to use. Either YAHOO.widget.Calendar.LONG, or YAHOO.widget.Calendar.SHORT. Defaults to YAHOO.widget.Calendar.LONG
+ *
initialFocus
String : Either "year" or "month" specifying which input control should get initial focus. Defaults to "year"
+ *
+ * @property _DEFAULT_CFG
+ * @protected
+ * @type Object
+ * @static
+ */
+ CN._DEFAULT_CFG = {
+ strings : {
+ month: "Month",
+ year: "Year",
+ submit: "Okay",
+ cancel: "Cancel",
+ invalidYear : "Year needs to be a number"
+ },
+ monthFormat: YAHOO.widget.Calendar.LONG,
+ initialFocus: "year"
+ };
+
+ /**
+ * The suffix added to the Calendar/CalendarGroup's ID, to generate
+ * a unique ID for the Navigator and it's bounding box.
+ * @property YAHOO.widget.CalendarNavigator.ID_SUFFIX
+ * @static
+ * @type String
+ * @final
+ */
+ CN.ID_SUFFIX = "_nav";
+ /**
+ * The suffix added to the Navigator's ID, to generate
+ * a unique ID for the month control.
+ * @property YAHOO.widget.CalendarNavigator.MONTH_SUFFIX
+ * @static
+ * @type String
+ * @final
+ */
+ CN.MONTH_SUFFIX = "_month";
+ /**
+ * The suffix added to the Navigator's ID, to generate
+ * a unique ID for the year control.
+ * @property YAHOO.widget.CalendarNavigator.YEAR_SUFFIX
+ * @static
+ * @type String
+ * @final
+ */
+ CN.YEAR_SUFFIX = "_year";
+ /**
+ * The suffix added to the Navigator's ID, to generate
+ * a unique ID for the error bounding box.
+ * @property YAHOO.widget.CalendarNavigator.ERROR_SUFFIX
+ * @static
+ * @type String
+ * @final
+ */
+ CN.ERROR_SUFFIX = "_error";
+ /**
+ * The suffix added to the Navigator's ID, to generate
+ * a unique ID for the "Cancel" button.
+ * @property YAHOO.widget.CalendarNavigator.CANCEL_SUFFIX
+ * @static
+ * @type String
+ * @final
+ */
+ CN.CANCEL_SUFFIX = "_cancel";
+ /**
+ * The suffix added to the Navigator's ID, to generate
+ * a unique ID for the "Submit" button.
+ * @property YAHOO.widget.CalendarNavigator.SUBMIT_SUFFIX
+ * @static
+ * @type String
+ * @final
+ */
+ CN.SUBMIT_SUFFIX = "_submit";
+
+ /**
+ * The number of digits to which the year input control is to be limited.
+ * @property YAHOO.widget.CalendarNavigator.YR_MAX_DIGITS
+ * @static
+ * @type Number
+ */
+ CN.YR_MAX_DIGITS = 4;
+
+ /**
+ * The amount by which to increment the current year value,
+ * when the arrow up/down key is pressed on the year control
+ * @property YAHOO.widget.CalendarNavigator.YR_MINOR_INC
+ * @static
+ * @type Number
+ */
+ CN.YR_MINOR_INC = 1;
+
+ /**
+ * The amount by which to increment the current year value,
+ * when the page up/down key is pressed on the year control
+ * @property YAHOO.widget.CalendarNavigator.YR_MAJOR_INC
+ * @static
+ * @type Number
+ */
+ CN.YR_MAJOR_INC = 10;
+
+ /**
+ * Artificial delay (in ms) between the time the Navigator is hidden
+ * and the Calendar/CalendarGroup state is updated. Allows the user
+ * the see the Calendar/CalendarGroup page changing. If set to 0
+ * the Calendar/CalendarGroup page will be updated instantly
+ * @property YAHOO.widget.CalendarNavigator.UPDATE_DELAY
+ * @static
+ * @type Number
+ */
+ CN.UPDATE_DELAY = 50;
+
+ /**
+ * Regular expression used to validate the year input
+ * @property YAHOO.widget.CalendarNavigator.YR_PATTERN
+ * @static
+ * @type RegExp
+ */
+ CN.YR_PATTERN = /^\d+$/;
+ /**
+ * Regular expression used to trim strings
+ * @property YAHOO.widget.CalendarNavigator.TRIM
+ * @static
+ * @type RegExp
+ */
+ CN.TRIM = /^\s*(.*?)\s*$/;
+})();
+
+YAHOO.widget.CalendarNavigator.prototype = {
+
+ /**
+ * The unique ID for this CalendarNavigator instance
+ * @property id
+ * @type String
+ */
+ id : null,
+
+ /**
+ * The Calendar/CalendarGroup instance to which the navigator belongs
+ * @property cal
+ * @type {Calendar|CalendarGroup}
+ */
+ cal : null,
+
+ /**
+ * Reference to the HTMLElement used to render the navigator's bounding box
+ * @property navEl
+ * @type HTMLElement
+ */
+ navEl : null,
+
+ /**
+ * Reference to the HTMLElement used to render the navigator's mask
+ * @property maskEl
+ * @type HTMLElement
+ */
+ maskEl : null,
+
+ /**
+ * Reference to the HTMLElement used to input the year
+ * @property yearEl
+ * @type HTMLElement
+ */
+ yearEl : null,
+
+ /**
+ * Reference to the HTMLElement used to input the month
+ * @property monthEl
+ * @type HTMLElement
+ */
+ monthEl : null,
+
+ /**
+ * Reference to the HTMLElement used to display validation errors
+ * @property errorEl
+ * @type HTMLElement
+ */
+ errorEl : null,
+
+ /**
+ * Reference to the HTMLElement used to update the Calendar/Calendar group
+ * with the month/year values
+ * @property submitEl
+ * @type HTMLElement
+ */
+ submitEl : null,
+
+ /**
+ * Reference to the HTMLElement used to hide the navigator without updating the
+ * Calendar/Calendar group
+ * @property cancelEl
+ * @type HTMLElement
+ */
+ cancelEl : null,
+
+ /**
+ * Reference to the first focusable control in the navigator (by default monthEl)
+ * @property firstCtrl
+ * @type HTMLElement
+ */
+ firstCtrl : null,
+
+ /**
+ * Reference to the last focusable control in the navigator (by default cancelEl)
+ * @property lastCtrl
+ * @type HTMLElement
+ */
+ lastCtrl : null,
+
+ /**
+ * The document containing the Calendar/Calendar group instance
+ * @protected
+ * @property _doc
+ * @type HTMLDocument
+ */
+ _doc : null,
+
+ /**
+ * Internal state property for the current year displayed in the navigator
+ * @protected
+ * @property _year
+ * @type Number
+ */
+ _year: null,
+
+ /**
+ * Internal state property for the current month index displayed in the navigator
+ * @protected
+ * @property _month
+ * @type Number
+ */
+ _month: 0,
+
+ /**
+ * Private internal state property which indicates whether or not the
+ * Navigator has been rendered.
+ * @private
+ * @property __rendered
+ * @type Boolean
+ */
+ __rendered: false,
+
+ /**
+ * Init lifecycle method called as part of construction
+ *
+ * @method init
+ * @param {Calendar} cal The instance of the Calendar or CalendarGroup to which this CalendarNavigator should be attached
+ */
+ init : function(cal) {
+ var calBox = cal.oDomContainer;
+
+ this.cal = cal;
+ this.id = calBox.id + YAHOO.widget.CalendarNavigator.ID_SUFFIX;
+ this._doc = calBox.ownerDocument;
+
+ /**
+ * Private flag, to identify IE6/IE7 Quirks
+ * @private
+ * @property __isIEQuirks
+ */
+ var ie = YAHOO.env.ua.ie;
+ this.__isIEQuirks = (ie && ((ie <= 6) || (ie === 7 && this._doc.compatMode == "BackCompat")));
+ },
+
+ /**
+ * Displays the navigator and mask, updating the input controls to reflect the
+ * currently set month and year. The show method will invoke the render method
+ * if the navigator has not been renderered already, allowing for lazy rendering
+ * of the control.
+ *
+ * The show method will fire the Calendar/CalendarGroup's beforeShowNav and showNav events
+ *
+ * @method show
+ */
+ show : function() {
+ var CLASSES = YAHOO.widget.CalendarNavigator.CLASSES;
+
+ if (this.cal.beforeShowNavEvent.fire()) {
+ if (!this.__rendered) {
+ this.render();
+ }
+ this.clearErrors();
+
+ this._updateMonthUI();
+ this._updateYearUI();
+ this._show(this.navEl, true);
+
+ this.setInitialFocus();
+ this.showMask();
+
+ YAHOO.util.Dom.addClass(this.cal.oDomContainer, CLASSES.NAV_VISIBLE);
+ this.cal.showNavEvent.fire();
+ }
+ },
+
+ /**
+ * Hides the navigator and mask
+ *
+ * The show method will fire the Calendar/CalendarGroup's beforeHideNav event and hideNav events
+ * @method hide
+ */
+ hide : function() {
+ var CLASSES = YAHOO.widget.CalendarNavigator.CLASSES;
+
+ if (this.cal.beforeHideNavEvent.fire()) {
+ this._show(this.navEl, false);
+ this.hideMask();
+ YAHOO.util.Dom.removeClass(this.cal.oDomContainer, CLASSES.NAV_VISIBLE);
+ this.cal.hideNavEvent.fire();
+ }
+ },
+
+
+ /**
+ * Displays the navigator's mask element
+ *
+ * @method showMask
+ */
+ showMask : function() {
+ this._show(this.maskEl, true);
+ if (this.__isIEQuirks) {
+ this._syncMask();
+ }
+ },
+
+ /**
+ * Hides the navigator's mask element
+ *
+ * @method hideMask
+ */
+ hideMask : function() {
+ this._show(this.maskEl, false);
+ },
+
+ /**
+ * Returns the current month set on the navigator
+ *
+ * Note: This may not be the month set in the UI, if
+ * the UI contains an invalid value.
+ *
+ * @method getMonth
+ * @return {Number} The Navigator's current month index
+ */
+ getMonth: function() {
+ return this._month;
+ },
+
+ /**
+ * Returns the current year set on the navigator
+ *
+ * Note: This may not be the year set in the UI, if
+ * the UI contains an invalid value.
+ *
+ * @method getYear
+ * @return {Number} The Navigator's current year value
+ */
+ getYear: function() {
+ return this._year;
+ },
+
+ /**
+ * Sets the current month on the Navigator, and updates the UI
+ *
+ * @method setMonth
+ * @param {Number} nMonth The month index, from 0 (Jan) through 11 (Dec).
+ */
+ setMonth : function(nMonth) {
+ if (nMonth >= 0 && nMonth < 12) {
+ this._month = nMonth;
+ }
+ this._updateMonthUI();
+ },
+
+ /**
+ * Sets the current year on the Navigator, and updates the UI. If the
+ * provided year is invalid, it will not be set.
+ *
+ * @method setYear
+ * @param {Number} nYear The full year value to set the Navigator to.
+ */
+ setYear : function(nYear) {
+ var yrPattern = YAHOO.widget.CalendarNavigator.YR_PATTERN;
+ if (YAHOO.lang.isNumber(nYear) && yrPattern.test(nYear+"")) {
+ this._year = nYear;
+ }
+ this._updateYearUI();
+ },
+
+ /**
+ * Renders the HTML for the navigator, adding it to the
+ * document and attaches event listeners if it has not
+ * already been rendered.
+ *
+ * @method render
+ */
+ render: function() {
+ this.cal.beforeRenderNavEvent.fire();
+ if (!this.__rendered) {
+ this.createNav();
+ this.createMask();
+ this.applyListeners();
+ this.__rendered = true;
+ }
+ this.cal.renderNavEvent.fire();
+ },
+
+ /**
+ * Creates the navigator's containing HTMLElement, it's contents, and appends
+ * the containg element to the Calendar/CalendarGroup's container.
+ *
+ * @method createNav
+ */
+ createNav : function() {
+ var NAV = YAHOO.widget.CalendarNavigator;
+ var doc = this._doc;
+
+ var d = doc.createElement("div");
+ d.className = NAV.CLASSES.NAV;
+
+ var htmlBuf = this.renderNavContents([]);
+
+ d.innerHTML = htmlBuf.join('');
+ this.cal.oDomContainer.appendChild(d);
+
+ this.navEl = d;
+
+ this.yearEl = doc.getElementById(this.id + NAV.YEAR_SUFFIX);
+ this.monthEl = doc.getElementById(this.id + NAV.MONTH_SUFFIX);
+ this.errorEl = doc.getElementById(this.id + NAV.ERROR_SUFFIX);
+ this.submitEl = doc.getElementById(this.id + NAV.SUBMIT_SUFFIX);
+ this.cancelEl = doc.getElementById(this.id + NAV.CANCEL_SUFFIX);
+
+ if (YAHOO.env.ua.gecko && this.yearEl && this.yearEl.type == "text") {
+ // Avoid XUL error on focus, select [ https://bugzilla.mozilla.org/show_bug.cgi?id=236791,
+ // supposedly fixed in 1.8.1, but there are reports of it still being around for methods other than blur ]
+ this.yearEl.setAttribute("autocomplete", "off");
+ }
+
+ this._setFirstLastElements();
+ },
+
+ /**
+ * Creates the Mask HTMLElement and appends it to the Calendar/CalendarGroups
+ * container.
+ *
+ * @method createMask
+ */
+ createMask : function() {
+ var C = YAHOO.widget.CalendarNavigator.CLASSES;
+
+ var d = this._doc.createElement("div");
+ d.className = C.MASK;
+
+ this.cal.oDomContainer.appendChild(d);
+ this.maskEl = d;
+ },
+
+ /**
+ * Used to set the width/height of the mask in pixels to match the Calendar Container.
+ * Currently only used for IE6 and IE7 quirks mode. The other A-Grade browser are handled using CSS (width/height 100%).
+ *
+ * The method is also registered as an HTMLElement resize listener on the Calendars container element.
+ *
+ * @protected
+ * @method _syncMask
+ */
+ _syncMask : function() {
+ var c = this.cal.oDomContainer;
+ if (c && this.maskEl) {
+ var r = YAHOO.util.Dom.getRegion(c);
+ YAHOO.util.Dom.setStyle(this.maskEl, "width", r.right - r.left + "px");
+ YAHOO.util.Dom.setStyle(this.maskEl, "height", r.bottom - r.top + "px");
+ }
+ },
+
+ /**
+ * Renders the contents of the navigator
+ *
+ * @method renderNavContents
+ *
+ * @param {Array} html The HTML buffer to append the HTML to.
+ * @return {Array} A reference to the buffer passed in.
+ */
+ renderNavContents : function(html) {
+ var NAV = YAHOO.widget.CalendarNavigator,
+ C = NAV.CLASSES,
+ h = html; // just to use a shorter name
+
+ h[h.length] = '
';
+ this.renderMonth(h);
+ h[h.length] = '
';
+ h[h.length] = '
';
+ this.renderYear(h);
+ h[h.length] = '
';
+ h[h.length] = '
';
+ this.renderButtons(h);
+ h[h.length] = '
';
+ h[h.length] = '';
+
+ return h;
+ },
+
+ /**
+ * Renders the month label and control for the navigator
+ *
+ * @method renderNavContents
+ * @param {Array} html The HTML buffer to append the HTML to.
+ * @return {Array} A reference to the buffer passed in.
+ */
+ renderMonth : function(html) {
+ var NAV = YAHOO.widget.CalendarNavigator,
+ C = NAV.CLASSES;
+
+ var id = this.id + NAV.MONTH_SUFFIX,
+ mf = this.__getCfg("monthFormat"),
+ months = this.cal.cfg.getProperty((mf == YAHOO.widget.Calendar.SHORT) ? "MONTHS_SHORT" : "MONTHS_LONG"),
+ h = html;
+
+ if (months && months.length > 0) {
+ h[h.length] = '';
+ h[h.length] = '';
+ }
+ return h;
+ },
+
+ /**
+ * Renders the year label and control for the navigator
+ *
+ * @method renderYear
+ * @param {Array} html The HTML buffer to append the HTML to.
+ * @return {Array} A reference to the buffer passed in.
+ */
+ renderYear : function(html) {
+ var NAV = YAHOO.widget.CalendarNavigator,
+ C = NAV.CLASSES;
+
+ var id = this.id + NAV.YEAR_SUFFIX,
+ size = NAV.YR_MAX_DIGITS,
+ h = html;
+
+ h[h.length] = '';
+ h[h.length] = '';
+ return h;
+ },
+
+ /**
+ * Renders the submit/cancel buttons for the navigator
+ *
+ * @method renderButton
+ * @return {String} The HTML created for the Button UI
+ */
+ renderButtons : function(html) {
+ var C = YAHOO.widget.CalendarNavigator.CLASSES;
+ var h = html;
+
+ h[h.length] = '';
+ h[h.length] = '';
+ h[h.length] = '';
+ h[h.length] = '';
+ h[h.length] = '';
+ h[h.length] = '';
+
+ return h;
+ },
+
+ /**
+ * Attaches DOM event listeners to the rendered elements
+ *
+ * The method will call applyKeyListeners, to setup keyboard specific
+ * listeners
+ *
+ * @method applyListeners
+ */
+ applyListeners : function() {
+ var E = YAHOO.util.Event;
+
+ function yearUpdateHandler() {
+ if (this.validate()) {
+ this.setYear(this._getYearFromUI());
+ }
+ }
+
+ function monthUpdateHandler() {
+ this.setMonth(this._getMonthFromUI());
+ }
+
+ E.on(this.submitEl, "click", this.submit, this, true);
+ E.on(this.cancelEl, "click", this.cancel, this, true);
+ E.on(this.yearEl, "blur", yearUpdateHandler, this, true);
+ E.on(this.monthEl, "change", monthUpdateHandler, this, true);
+
+ if (this.__isIEQuirks) {
+ YAHOO.util.Event.on(this.cal.oDomContainer, "resize", this._syncMask, this, true);
+ }
+
+ this.applyKeyListeners();
+ },
+
+ /**
+ * Removes/purges DOM event listeners from the rendered elements
+ *
+ * @method purgeListeners
+ */
+ purgeListeners : function() {
+ var E = YAHOO.util.Event;
+ E.removeListener(this.submitEl, "click", this.submit);
+ E.removeListener(this.cancelEl, "click", this.cancel);
+ E.removeListener(this.yearEl, "blur");
+ E.removeListener(this.monthEl, "change");
+ if (this.__isIEQuirks) {
+ E.removeListener(this.cal.oDomContainer, "resize", this._syncMask);
+ }
+
+ this.purgeKeyListeners();
+ },
+
+ /**
+ * Attaches DOM listeners for keyboard support.
+ * Tab/Shift-Tab looping, Enter Key Submit on Year element,
+ * Up/Down/PgUp/PgDown year increment on Year element
+ *
+ * NOTE: MacOSX Safari 2.x doesn't let you tab to buttons and
+ * MacOSX Gecko does not let you tab to buttons or select controls,
+ * so for these browsers, Tab/Shift-Tab looping is limited to the
+ * elements which can be reached using the tab key.
+ *
+ * @method applyKeyListeners
+ */
+ applyKeyListeners : function() {
+ var E = YAHOO.util.Event,
+ ua = YAHOO.env.ua;
+
+ // IE/Safari 3.1 doesn't fire keypress for arrow/pg keys (non-char keys)
+ var arrowEvt = (ua.ie || ua.webkit) ? "keydown" : "keypress";
+
+ // - IE/Safari 3.1 doesn't fire keypress for non-char keys
+ // - Opera doesn't allow us to cancel keydown or keypress for tab, but
+ // changes focus successfully on keydown (keypress is too late to change focus - opera's already moved on).
+ var tabEvt = (ua.ie || ua.opera || ua.webkit) ? "keydown" : "keypress";
+
+ // Everyone likes keypress for Enter (char keys) - whoo hoo!
+ E.on(this.yearEl, "keypress", this._handleEnterKey, this, true);
+
+ E.on(this.yearEl, arrowEvt, this._handleDirectionKeys, this, true);
+ E.on(this.lastCtrl, tabEvt, this._handleTabKey, this, true);
+ E.on(this.firstCtrl, tabEvt, this._handleShiftTabKey, this, true);
+ },
+
+ /**
+ * Removes/purges DOM listeners for keyboard support
+ *
+ * @method purgeKeyListeners
+ */
+ purgeKeyListeners : function() {
+ var E = YAHOO.util.Event,
+ ua = YAHOO.env.ua;
+
+ var arrowEvt = (ua.ie || ua.webkit) ? "keydown" : "keypress";
+ var tabEvt = (ua.ie || ua.opera || ua.webkit) ? "keydown" : "keypress";
+
+ E.removeListener(this.yearEl, "keypress", this._handleEnterKey);
+ E.removeListener(this.yearEl, arrowEvt, this._handleDirectionKeys);
+ E.removeListener(this.lastCtrl, tabEvt, this._handleTabKey);
+ E.removeListener(this.firstCtrl, tabEvt, this._handleShiftTabKey);
+ },
+
+ /**
+ * Updates the Calendar/CalendarGroup's pagedate with the currently set month and year if valid.
+ *
+ * If the currently set month/year is invalid, a validation error will be displayed and the
+ * Calendar/CalendarGroup's pagedate will not be updated.
+ *
+ * @method submit
+ */
+ submit : function() {
+ if (this.validate()) {
+ this.hide();
+
+ this.setMonth(this._getMonthFromUI());
+ this.setYear(this._getYearFromUI());
+
+ var cal = this.cal;
+ var nav = this;
+
+ function update() {
+ cal.setYear(nav.getYear());
+ cal.setMonth(nav.getMonth());
+ cal.render();
+ }
+ // Artificial delay, just to help the user see something changed
+ var delay = YAHOO.widget.CalendarNavigator.UPDATE_DELAY;
+ if (delay > 0) {
+ window.setTimeout(update, delay);
+ } else {
+ update();
+ }
+ }
+ },
+
+ /**
+ * Hides the navigator and mask, without updating the Calendar/CalendarGroup's state
+ *
+ * @method cancel
+ */
+ cancel : function() {
+ this.hide();
+ },
+
+ /**
+ * Validates the current state of the UI controls
+ *
+ * @method validate
+ * @return {Boolean} true, if the current UI state contains valid values, false if not
+ */
+ validate : function() {
+ if (this._getYearFromUI() !== null) {
+ this.clearErrors();
+ return true;
+ } else {
+ this.setYearError();
+ this.setError(this.__getCfg("invalidYear", true));
+ return false;
+ }
+ },
+
+ /**
+ * Displays an error message in the Navigator's error panel
+ * @method setError
+ * @param {String} msg The error message to display
+ */
+ setError : function(msg) {
+ if (this.errorEl) {
+ this.errorEl.innerHTML = msg;
+ this._show(this.errorEl, true);
+ }
+ },
+
+ /**
+ * Clears the navigator's error message and hides the error panel
+ * @method clearError
+ */
+ clearError : function() {
+ if (this.errorEl) {
+ this.errorEl.innerHTML = "";
+ this._show(this.errorEl, false);
+ }
+ },
+
+ /**
+ * Displays the validation error UI for the year control
+ * @method setYearError
+ */
+ setYearError : function() {
+ YAHOO.util.Dom.addClass(this.yearEl, YAHOO.widget.CalendarNavigator.CLASSES.INVALID);
+ },
+
+ /**
+ * Removes the validation error UI for the year control
+ * @method clearYearError
+ */
+ clearYearError : function() {
+ YAHOO.util.Dom.removeClass(this.yearEl, YAHOO.widget.CalendarNavigator.CLASSES.INVALID);
+ },
+
+ /**
+ * Clears all validation and error messages in the UI
+ * @method clearErrors
+ */
+ clearErrors : function() {
+ this.clearError();
+ this.clearYearError();
+ },
+
+ /**
+ * Sets the initial focus, based on the configured value
+ * @method setInitialFocus
+ */
+ setInitialFocus : function() {
+ var el = this.submitEl,
+ f = this.__getCfg("initialFocus");
+
+ if (f && f.toLowerCase) {
+ f = f.toLowerCase();
+ if (f == "year") {
+ el = this.yearEl;
+ try {
+ this.yearEl.select();
+ } catch (err) {
+ // Ignore;
+ }
+ } else if (f == "month") {
+ el = this.monthEl;
+ }
+ }
+
+ if (el && YAHOO.lang.isFunction(el.focus)) {
+ try {
+ el.focus();
+ } catch (err) {
+ // TODO: Fall back if focus fails?
+ }
+ }
+ },
+
+ /**
+ * Removes all renderered HTML elements for the Navigator from
+ * the DOM, purges event listeners and clears (nulls) any property
+ * references to HTML references
+ * @method erase
+ */
+ erase : function() {
+ if (this.__rendered) {
+ this.purgeListeners();
+
+ // Clear out innerHTML references
+ this.yearEl = null;
+ this.monthEl = null;
+ this.errorEl = null;
+ this.submitEl = null;
+ this.cancelEl = null;
+ this.firstCtrl = null;
+ this.lastCtrl = null;
+ if (this.navEl) {
+ this.navEl.innerHTML = "";
+ }
+
+ var p = this.navEl.parentNode;
+ if (p) {
+ p.removeChild(this.navEl);
+ }
+ this.navEl = null;
+
+ var pm = this.maskEl.parentNode;
+ if (pm) {
+ pm.removeChild(this.maskEl);
+ }
+ this.maskEl = null;
+ this.__rendered = false;
+ }
+ },
+
+ /**
+ * Destroys the Navigator object and any HTML references
+ * @method destroy
+ */
+ destroy : function() {
+ this.erase();
+ this._doc = null;
+ this.cal = null;
+ this.id = null;
+ },
+
+ /**
+ * Protected implementation to handle how UI elements are
+ * hidden/shown.
+ *
+ * @method _show
+ * @protected
+ */
+ _show : function(el, bShow) {
+ if (el) {
+ YAHOO.util.Dom.setStyle(el, "display", (bShow) ? "block" : "none");
+ }
+ },
+
+ /**
+ * Returns the month value (index), from the month UI element
+ * @protected
+ * @method _getMonthFromUI
+ * @return {Number} The month index, or 0 if a UI element for the month
+ * is not found
+ */
+ _getMonthFromUI : function() {
+ if (this.monthEl) {
+ return this.monthEl.selectedIndex;
+ } else {
+ return 0; // Default to Jan
+ }
+ },
+
+ /**
+ * Returns the year value, from the Navitator's year UI element
+ * @protected
+ * @method _getYearFromUI
+ * @return {Number} The year value set in the UI, if valid. null is returned if
+ * the UI does not contain a valid year value.
+ */
+ _getYearFromUI : function() {
+ var NAV = YAHOO.widget.CalendarNavigator;
+
+ var yr = null;
+ if (this.yearEl) {
+ var value = this.yearEl.value;
+ value = value.replace(NAV.TRIM, "$1");
+
+ if (NAV.YR_PATTERN.test(value)) {
+ yr = parseInt(value, 10);
+ }
+ }
+ return yr;
+ },
+
+ /**
+ * Updates the Navigator's year UI, based on the year value set on the Navigator object
+ * @protected
+ * @method _updateYearUI
+ */
+ _updateYearUI : function() {
+ if (this.yearEl && this._year !== null) {
+ this.yearEl.value = this._year;
+ }
+ },
+
+ /**
+ * Updates the Navigator's month UI, based on the month value set on the Navigator object
+ * @protected
+ * @method _updateMonthUI
+ */
+ _updateMonthUI : function() {
+ if (this.monthEl) {
+ this.monthEl.selectedIndex = this._month;
+ }
+ },
+
+ /**
+ * Sets up references to the first and last focusable element in the Navigator's UI
+ * in terms of tab order (Naviagator's firstEl and lastEl properties). The references
+ * are used to control modality by looping around from the first to the last control
+ * and visa versa for tab/shift-tab navigation.
+ *
+ * @protected
+ * @method _setFirstLastElements
+ */
+ _setFirstLastElements : function() {
+ this.firstCtrl = this.monthEl;
+ this.lastCtrl = this.cancelEl;
+
+ // Special handling for MacOSX.
+ // - Safari 2.x can't focus on buttons
+ // - Gecko can't focus on select boxes or buttons
+ if (this.__isMac) {
+ if (YAHOO.env.ua.webkit && YAHOO.env.ua.webkit < 420){
+ this.firstCtrl = this.monthEl;
+ this.lastCtrl = this.yearEl;
+ }
+ if (YAHOO.env.ua.gecko) {
+ this.firstCtrl = this.yearEl;
+ this.lastCtrl = this.yearEl;
+ }
+ }
+ },
+
+ /**
+ * Default Keyboard event handler to capture Enter
+ * on the Navigator's year control (yearEl)
+ *
+ * @method _handleEnterKey
+ * @protected
+ * @param {Event} e The DOM event being handled
+ */
+ _handleEnterKey : function(e) {
+ var KEYS = YAHOO.util.KeyListener.KEY;
+
+ if (YAHOO.util.Event.getCharCode(e) == KEYS.ENTER) {
+ YAHOO.util.Event.preventDefault(e);
+ this.submit();
+ }
+ },
+
+ /**
+ * Default Keyboard event handler to capture up/down/pgup/pgdown
+ * on the Navigator's year control (yearEl).
+ *
+ * @method _handleDirectionKeys
+ * @protected
+ * @param {Event} e The DOM event being handled
+ */
+ _handleDirectionKeys : function(e) {
+ var E = YAHOO.util.Event,
+ KEYS = YAHOO.util.KeyListener.KEY,
+ NAV = YAHOO.widget.CalendarNavigator;
+
+ var value = (this.yearEl.value) ? parseInt(this.yearEl.value, 10) : null;
+ if (isFinite(value)) {
+ var dir = false;
+ switch(E.getCharCode(e)) {
+ case KEYS.UP:
+ this.yearEl.value = value + NAV.YR_MINOR_INC;
+ dir = true;
+ break;
+ case KEYS.DOWN:
+ this.yearEl.value = Math.max(value - NAV.YR_MINOR_INC, 0);
+ dir = true;
+ break;
+ case KEYS.PAGE_UP:
+ this.yearEl.value = value + NAV.YR_MAJOR_INC;
+ dir = true;
+ break;
+ case KEYS.PAGE_DOWN:
+ this.yearEl.value = Math.max(value - NAV.YR_MAJOR_INC, 0);
+ dir = true;
+ break;
+ default:
+ break;
+ }
+ if (dir) {
+ E.preventDefault(e);
+ try {
+ this.yearEl.select();
+ } catch(err) {
+ // Ignore
+ }
+ }
+ }
+ },
+
+ /**
+ * Default Keyboard event handler to capture Tab
+ * on the last control (lastCtrl) in the Navigator.
+ *
+ * @method _handleTabKey
+ * @protected
+ * @param {Event} e The DOM event being handled
+ */
+ _handleTabKey : function(e) {
+ var E = YAHOO.util.Event,
+ KEYS = YAHOO.util.KeyListener.KEY;
+
+ if (E.getCharCode(e) == KEYS.TAB && !e.shiftKey) {
+ try {
+ E.preventDefault(e);
+ this.firstCtrl.focus();
+ } catch (err) {
+ // Ignore - mainly for focus edge cases
+ }
+ }
+ },
+
+ /**
+ * Default Keyboard event handler to capture Shift-Tab
+ * on the first control (firstCtrl) in the Navigator.
+ *
+ * @method _handleShiftTabKey
+ * @protected
+ * @param {Event} e The DOM event being handled
+ */
+ _handleShiftTabKey : function(e) {
+ var E = YAHOO.util.Event,
+ KEYS = YAHOO.util.KeyListener.KEY;
+
+ if (e.shiftKey && E.getCharCode(e) == KEYS.TAB) {
+ try {
+ E.preventDefault(e);
+ this.lastCtrl.focus();
+ } catch (err) {
+ // Ignore - mainly for focus edge cases
+ }
+ }
+ },
+
+ /**
+ * Retrieve Navigator configuration values from
+ * the parent Calendar/CalendarGroup's config value.
+ *
+ * If it has not been set in the user provided configuration, the method will
+ * return the default value of the configuration property, as set in _DEFAULT_CFG
+ *
+ * @private
+ * @method __getCfg
+ * @param {String} Case sensitive property name.
+ * @param {Boolean} true, if the property is a string property, false if not.
+ * @return The value of the configuration property
+ */
+ __getCfg : function(prop, bIsStr) {
+ var DEF_CFG = YAHOO.widget.CalendarNavigator._DEFAULT_CFG;
+ var cfg = this.cal.cfg.getProperty("navigator");
+
+ if (bIsStr) {
+ return (cfg !== true && cfg.strings && cfg.strings[prop]) ? cfg.strings[prop] : DEF_CFG.strings[prop];
+ } else {
+ return (cfg !== true && cfg[prop]) ? cfg[prop] : DEF_CFG[prop];
+ }
+ },
+
+ /**
+ * Private flag, to identify MacOS
+ * @private
+ * @property __isMac
+ */
+ __isMac : (navigator.userAgent.toLowerCase().indexOf("macintosh") != -1)
+
+};
+
+YAHOO.register("calendar", YAHOO.widget.Calendar, {version: "2.5.2", build: "1076"});
diff --git a/lib/yui/charts/README b/lib/yui/charts/README
index 2c88c6ca4e..1f9048b4c8 100644
--- a/lib/yui/charts/README
+++ b/lib/yui/charts/README
@@ -1,5 +1,17 @@
YUI Library - Charts - Release Notes
+2.5.2
+ * Support for legends
+ * New series styles connectPoints, connectDiscontinuousPoints, and discontinuousDashLength
+ * dataTipFunction, xAxisLabelFunction, and yAxisLabelFunction attributes now support function references
+ * Added destroy() function.
+ * Changed majorTicks and minorTicks substyle "position" to "display". New option "none" will hide ticks.
+ * When polling is enabled, the chart now makes an immediate request instead of waiting for the first interval.
+ * Includes ActionScript source files and sample Ant build file.
+
+2.5.1
+ * No changes
+
2.5.0
* Added lineSize style to series styles
* Added showLabels substyle to xAxis and yAxis styles
diff --git a/lib/yui/charts/assets/charts.swf b/lib/yui/charts/assets/charts.swf
index 9e9597a91f..4de14589f4 100644
Binary files a/lib/yui/charts/assets/charts.swf and b/lib/yui/charts/assets/charts.swf differ
diff --git a/lib/yui/charts/charts-experimental-debug.js b/lib/yui/charts/charts-experimental-debug.js
index f936fa0121..e63bd57dc7 100644
--- a/lib/yui/charts/charts-experimental-debug.js
+++ b/lib/yui/charts/charts-experimental-debug.js
@@ -2,8 +2,9 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
+/*extern ActiveXObject, __flash_unloadHandler, __flash_savedUnloadHandler */
/*!
* SWFObject v1.5: Flash Player detection and embed - http://blog.deconcept.com/swfobject/
*
@@ -352,9 +353,10 @@ YAHOO.widget.FlashAdapter = function(swfURL, containerID, attributes)
this._attributes = attributes;
this._swfURL = swfURL;
+ this._containerID = containerID;
//embed the SWF file in the page
- this._embedSWF(this._swfURL, containerID, attributes.id, attributes.version,
+ this._embedSWF(this._swfURL, this._containerID, attributes.id, attributes.version,
attributes.backgroundColor, attributes.expressInstall, attributes.wmode);
/**
@@ -374,6 +376,14 @@ YAHOO.extend(YAHOO.widget.FlashAdapter, YAHOO.util.AttributeProvider,
*/
_swfURL: null,
+ /**
+ * The ID of the containing DIV.
+ * @property _containerID
+ * @type String
+ * @private
+ */
+ _containerID: null,
+
/**
* A reference to the embedded SWF file.
* @property _swf
@@ -408,6 +418,37 @@ YAHOO.extend(YAHOO.widget.FlashAdapter, YAHOO.util.AttributeProvider,
return "FlashAdapter " + this._id;
},
+ /**
+ * Nulls out the entire FlashAdapter instance and related objects and removes attached
+ * event listeners and clears out DOM elements inside the container. After calling
+ * this method, the instance reference should be expliclitly nulled by implementer,
+ * as in myChart = null. Use with caution!
+ *
+ * @method destroy
+ */
+ destroy: function()
+ {
+ //kill the Flash Player instance
+ if(this._swf)
+ {
+ var container = YAHOO.util.Dom.get(this._containerID);
+ container.removeChild(this._swf);
+ }
+
+ var instanceName = this._id;
+
+ //null out properties
+ for(var prop in this)
+ {
+ if(YAHOO.lang.hasOwnProperty(this, prop))
+ {
+ this[prop] = null;
+ }
+ }
+
+ YAHOO.log("FlashAdapter instance destroyed: " + instanceName);
+ },
+
/**
* Embeds the SWF in the page and associates it with this instance.
*
@@ -503,10 +544,41 @@ YAHOO.extend(YAHOO.widget.FlashAdapter, YAHOO.util.AttributeProvider,
_initAttributes: function(attributes)
{
//should be overridden if other attributes need to be set up
+
+ /**
+ * @attribute wmode
+ * @description Sets the window mode of the Flash Player control. May be
+ * "window", "opaque", or "transparent". Only available in the constructor
+ * because it may not be set after Flash Player has been embedded in the page.
+ * @type String
+ */
+
+ /**
+ * @attribute expressInstall
+ * @description URL pointing to a SWF file that handles Flash Player's express
+ * install feature. Only available in the constructor because it may not be
+ * set after Flash Player has been embedded in the page.
+ * @type String
+ */
+
+ /**
+ * @attribute version
+ * @description Minimum required version for the SWF file. Only available in the constructor because it may not be
+ * set after Flash Player has been embedded in the page.
+ * @type String
+ */
+
+ /**
+ * @attribute backgroundColor
+ * @description The background color of the SWF. Only available in the constructor because it may not be
+ * set after Flash Player has been embedded in the page.
+ * @type String
+ */
/**
* @attribute swfURL
- * @description Absolute or relative URL to the SWF displayed by the FlashAdapter.
+ * @description Absolute or relative URL to the SWF displayed by the FlashAdapter. Only available in the constructor because it may not be
+ * set after Flash Player has been embedded in the page.
* @type String
*/
this.getAttributeConfig("swfURL",
@@ -549,6 +621,52 @@ YAHOO.widget.FlashAdapter.eventHandler = function(elementID, event)
}
};
+/**
+ * The number of proxy functions that have been created.
+ * @static
+ * @private
+ */
+YAHOO.widget.FlashAdapter.proxyFunctionCount = 0;
+
+/**
+ * Creates a globally accessible function that wraps a function reference.
+ * Returns the proxy function's name as a string for use by the SWF through
+ * ExternalInterface.
+ *
+ * @method YAHOO.widget.FlashAdapter.createProxyFunction
+ * @static
+ * @private
+ */
+YAHOO.widget.FlashAdapter.createProxyFunction = function(func)
+{
+ var index = YAHOO.widget.FlashAdapter.proxyFunctionCount;
+ YAHOO.widget.FlashAdapter["proxyFunction" + index] = function()
+ {
+ return func.apply(null, arguments);
+ };
+ YAHOO.widget.FlashAdapter.proxyFunctionCount++;
+ return "YAHOO.widget.FlashAdapter.proxyFunction" + index.toString();
+};
+
+/**
+ * Removes a function created with createProxyFunction()
+ *
+ * @method YAHOO.widget.FlashAdapter.removeProxyFunction
+ * @static
+ * @private
+ */
+YAHOO.widget.FlashAdapter.removeProxyFunction = function(funcName)
+{
+ //quick error check
+ if(!funcName || funcName.indexOf("YAHOO.widget.FlashAdapter.proxyFunction") < 0)
+ {
+ return;
+ }
+
+ funcName = funcName.substr(26);
+ YAHOO.widget.FlashAdapter[funcName] = null;
+};
+
/**
* The Charts widget provides a Flash control for displaying data
* graphically by series across A-grade browsers with Flash Player installed.
@@ -705,6 +823,15 @@ YAHOO.extend(YAHOO.widget.Chart, YAHOO.widget.FlashAdapter,
*/
_initialized: false,
+ /**
+ * Stores a reference to the dataTipFunction created by
+ * YAHOO.widget.FlashAdapter.createProxyFunction()
+ * @property _dataTipFunction
+ * @type String
+ * @private
+ */
+ _dataTipFunction: null,
+
/**
* Public accessor to the unique name of the Chart instance.
*
@@ -762,6 +889,28 @@ YAHOO.extend(YAHOO.widget.Chart, YAHOO.widget.FlashAdapter,
this._swf.setSeriesStyles(styles);
},
+ destroy: function()
+ {
+ //stop polling if needed
+ if(this._dataSource !== null)
+ {
+ if(this._pollingID !== null)
+ {
+ this._dataSource.clearInterval(this._pollingID);
+ this._pollingID = null;
+ }
+ }
+
+ //remove proxy functions
+ if(this._dataTipFunction)
+ {
+ YAHOO.widget.FlashAdapter.removeProxyFunction(this._dataTipFunction);
+ }
+
+ //call last
+ YAHOO.widget.Chart.superclass.destroy.call(this);
+ },
+
/**
* Initializes the attributes.
*
@@ -919,10 +1068,7 @@ YAHOO.extend(YAHOO.widget.Chart, YAHOO.widget.FlashAdapter,
{
this._pollingID = this._dataSource.setInterval(this._pollingInterval, this._request, this._loadDataHandler, this);
}
- else
- {
- this._dataSource.sendRequest(this._request, this._loadDataHandler, this);
- }
+ this._dataSource.sendRequest(this._request, this._loadDataHandler, this);
}
},
@@ -958,18 +1104,21 @@ YAHOO.extend(YAHOO.widget.Chart, YAHOO.widget.FlashAdapter,
var clonedSeries = {};
for(var prop in currentSeries)
{
- if(prop == "style" && currentSeries.style !== null)
- {
- clonedSeries.style = YAHOO.lang.JSON.stringify(currentSeries.style);
- styleChanged = true;
-
- //we don't want to modify the styles again next time
- //so null out the style property.
- currentSeries.style = null;
- }
- else
+ if(YAHOO.lang.hasOwnProperty(currentSeries, prop))
{
- clonedSeries[prop] = currentSeries[prop];
+ if(prop == "style" && currentSeries.style !== null)
+ {
+ clonedSeries.style = YAHOO.lang.JSON.stringify(currentSeries.style);
+ styleChanged = true;
+
+ //we don't want to modify the styles again next time
+ //so null out the style property.
+ currentSeries.style = null;
+ }
+ else
+ {
+ clonedSeries[prop] = currentSeries[prop];
+ }
}
}
dataProvider.push(clonedSeries);
@@ -1098,7 +1247,7 @@ YAHOO.extend(YAHOO.widget.Chart, YAHOO.widget.FlashAdapter,
*/
_getCategoryNames: function()
{
- return this._swf.getCategoryNames();
+ this._swf.getCategoryNames();
},
/**
@@ -1112,25 +1261,6 @@ YAHOO.extend(YAHOO.widget.Chart, YAHOO.widget.FlashAdapter,
this._swf.setCategoryNames(value);
},
- /**
- * Storage for the dataTipFunction attribute.
- *
- * @property _dataTipFunction
- * @private
- */
- _dataTipFunction: null,
-
- /**
- * Getter for the dataTipFunction attribute.
- *
- * @method _getDataTipFunction
- * @private
- */
- _getDataTipFunction: function()
- {
- return this._dataTipFunction;
- },
-
/**
* Setter for the dataTipFunction attribute.
*
@@ -1139,7 +1269,16 @@ YAHOO.extend(YAHOO.widget.Chart, YAHOO.widget.FlashAdapter,
*/
_setDataTipFunction: function(value)
{
- this._dataTipFunction = value;
+ if(this._dataTipFunction)
+ {
+ YAHOO.widget.FlashAdapter.removeProxyFunction(this._dataTipFunction);
+ }
+
+ if(value && typeof value == "function")
+ {
+ value = YAHOO.widget.FlashAdapter.createProxyFunction(value);
+ this._dataTipFunction = value;
+ }
this._swf.setDataTipFunction(value);
},
@@ -1183,7 +1322,7 @@ YAHOO.widget.Chart.SWFURL = "assets/charts.swf";
*
* @namespace YAHOO.widget
* @class PieChart
- * @uses YAHOO.widget.CartesianChart
+ * @uses YAHOO.widget.Chart
* @constructor
* @param containerId {HTMLElement} Container element for the Flash Player instance.
* @param dataSource {YAHOO.util.DataSource} DataSource instance.
@@ -1303,6 +1442,43 @@ YAHOO.lang.extend(YAHOO.widget.PieChart, YAHOO.widget.Chart,
YAHOO.lang.extend(YAHOO.widget.CartesianChart, YAHOO.widget.Chart,
{
+ /**
+ * Stores a reference to the xAxis labelFunction created by
+ * YAHOO.widget.FlashAdapter.createProxyFunction()
+ * @property _xAxisLabelFunction
+ * @type String
+ * @private
+ */
+ _xAxisLabelFunction: null,
+
+ /**
+ * Stores a reference to the yAxis labelFunction created by
+ * YAHOO.widget.FlashAdapter.createProxyFunction()
+ * @property _yAxisLabelFunction
+ * @type String
+ * @private
+ */
+ _yAxisLabelFunction: null,
+
+ destroy: function()
+ {
+ //remove proxy functions
+ if(this._xAxisLabelFunction)
+ {
+ YAHOO.widget.FlashAdapter.removeProxyFunction(this._xAxisLabelFunction);
+ this._xAxisLabelFunction = null;
+ }
+
+ if(this._yAxisLabelFunction)
+ {
+ YAHOO.widget.FlashAdapter.removeProxyFunction(this._yAxisLabelFunction);
+ this._yAxisLabelFunction = null;
+ }
+
+ //call last
+ YAHOO.widget.CartesianChart.superclass.destroy.call(this);
+ },
+
/**
* Initializes the attributes.
*
@@ -1418,6 +1594,16 @@ YAHOO.lang.extend(YAHOO.widget.CartesianChart, YAHOO.widget.Chart,
*/
_setXAxis: function(value)
{
+ if(this._xAxisLabelFunction)
+ {
+ YAHOO.widget.FlashAdapter.removeProxyFunction(this._xAxisLabelFunction);
+ }
+
+ if(value.labelFunction && typeof value.labelFunction == "function")
+ {
+ value.labelFunction = YAHOO.widget.FlashAdapter.createProxyFunction(value);
+ this._xAxisLabelFunction = value.labelFunction;
+ }
this._swf.setHorizontalAxis(value);
},
@@ -1429,6 +1615,16 @@ YAHOO.lang.extend(YAHOO.widget.CartesianChart, YAHOO.widget.Chart,
*/
_setYAxis: function(value)
{
+ if(this._yAxisLabelFunction)
+ {
+ YAHOO.widget.FlashAdapter.removeProxyFunction(this._yAxisLabelFunction);
+ }
+
+ if(value.labelFunction && typeof value.labelFunction == "function")
+ {
+ value.labelFunction = YAHOO.widget.FlashAdapter.createProxyFunction(value.labelFunction);
+ this._yAxisLabelFunction = value.labelFunction;
+ }
this._swf.setVerticalAxis(value);
}
});
@@ -1853,4 +2049,4 @@ YAHOO.lang.extend(YAHOO.widget.PieSeries, YAHOO.widget.Series,
categoryField: null
});
-YAHOO.register("charts", YAHOO.widget.Chart, {version: "2.5.0", build: "895"});
+YAHOO.register("charts", YAHOO.widget.Chart, {version: "2.5.2", build: "1076"});
diff --git a/lib/yui/charts/charts-experimental-min.js b/lib/yui/charts/charts-experimental-min.js
index 4f702b14db..d3c57a2a2d 100644
--- a/lib/yui/charts/charts-experimental-min.js
+++ b/lib/yui/charts/charts-experimental-min.js
@@ -2,7 +2,7 @@
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
-version: 2.5.0
+version: 2.5.2
*/
/*
* SWFObject v1.5: Flash Player detection and embed - http://blog.deconcept.com/swfobject/
@@ -11,5 +11,6 @@ version: 2.5.0
* http://www.opensource.org/licenses/mit-license.php
*/
var deconcept=deconcept||{};if(typeof deconcept.util=="undefined"||!deconcept.util){deconcept.util={};}if(typeof deconcept.SWFObjectUtil=="undefined"||!deconcept.SWFObjectUtil){deconcept.SWFObjectUtil={};}deconcept.SWFObject=function(E,C,K,F,H,J,L,G,A,D){if(!document.getElementById){return ;}this.DETECT_KEY=D?D:"detectflash";this.skipDetect=deconcept.util.getRequestParameter(this.DETECT_KEY);this.params={};this.variables={};this.attributes=[];if(E){this.setAttribute("swf",E);}if(C){this.setAttribute("id",C);}if(K){this.setAttribute("width",K);}if(F){this.setAttribute("height",F);}if(H){this.setAttribute("version",new deconcept.PlayerVersion(H.toString().split(".")));}this.installedVer=deconcept.SWFObjectUtil.getPlayerVersion();if(!window.opera&&document.all&&this.installedVer.major>7){deconcept.SWFObject.doPrepUnload=true;}if(J){this.addParam("bgcolor",J);}var B=L?L:"high";this.addParam("quality",B);this.setAttribute("useExpressInstall",false);this.setAttribute("doExpressInstall",false);var I=(G)?G:window.location;this.setAttribute("xiRedirectUrl",I);this.setAttribute("redirectUrl","");if(A){this.setAttribute("redirectUrl",A);}};deconcept.SWFObject.prototype={useExpressInstall:function(A){this.xiSWFPath=!A?"expressinstall.swf":A;this.setAttribute("useExpressInstall",true);},setAttribute:function(A,B){this.attributes[A]=B;},getAttribute:function(A){return this.attributes[A];},addParam:function(A,B){this.params[A]=B;},getParams:function(){return this.params;},addVariable:function(A,B){this.variables[A]=B;},getVariable:function(A){return this.variables[A];},getVariables:function(){return this.variables;},getVariablePairs:function(){var A=[];var B;var C=this.getVariables();for(B in C){A[A.length]=B+"="+C[B];}return A;},getSWFHTML:function(){var D="";var C={};var A="";var B="";if(navigator.plugins&&navigator.mimeTypes&&navigator.mimeTypes.length){if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","PlugIn");this.setAttribute("swf",this.xiSWFPath);}D='