Index: openacs-4/packages/ajaxhelper/www/resources/yui/calendar/calendar.js =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/ajaxhelper/www/resources/yui/calendar/calendar.js,v diff -u -r1.3 -r1.4 --- openacs-4/packages/ajaxhelper/www/resources/yui/calendar/calendar.js 8 Sep 2007 14:22:00 -0000 1.3 +++ openacs-4/packages/ajaxhelper/www/resources/yui/calendar/calendar.js 9 Apr 2009 17:03:49 -0000 1.4 @@ -1,8 +1,8 @@ /* -Copyright (c) 2007, Yahoo! Inc. All rights reserved. +Copyright (c) 2009, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt -version: 2.3.0 +version: 2.7.0 */ (function () { @@ -18,26 +18,20 @@ * @param {Object} owner The owner Object to which this Config Object belongs */ YAHOO.util.Config = function (owner) { - + if (owner) { - this.init(owner); - } - - if (!owner) { - - - } - + + }; var Lang = YAHOO.lang, - CustomEvent = YAHOO.util.CustomEvent, + 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 @@ -203,16 +197,19 @@ getConfig: function () { var cfg = {}, + currCfg = this.config, prop, property; - for (prop in this.config) { - property = this.config[prop]; - if (property && property.event) { - cfg[prop] = property.value; + for (prop in currCfg) { + if (Lang.hasOwnProperty(currCfg, prop)) { + property = currCfg[prop]; + if (property && property.event) { + cfg[prop] = property.value; + } } } - + return cfg; }, @@ -249,11 +246,11 @@ !Lang.isUndefined(this.initialConfig[key])) { this.setProperty(key, this.initialConfig[key]); + + return true; } - return true; - } else { return false; @@ -353,7 +350,7 @@ if (queueItem) { queueItemKey = queueItem[0]; queueItemValue = queueItem[1]; - + if (queueItemKey == key) { /* @@ -381,37 +378,37 @@ } 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; @@ -459,13 +456,23 @@ */ applyConfig: function (userConfig, init) { - var prop; - + var sKey, + oConfig; + if (init) { - this.initialConfig = userConfig; + oConfig = {}; + for (sKey in userConfig) { + if (Lang.hasOwnProperty(userConfig, sKey)) { + oConfig[sKey.toLowerCase()] = userConfig[sKey]; + } + } + this.initialConfig = oConfig; } - for (prop in userConfig) { - this.queueProperty(prop, userConfig[prop]); + + for (sKey in userConfig) { + if (Lang.hasOwnProperty(userConfig, sKey)) { + this.queueProperty(sKey, userConfig[sKey]); + } } }, @@ -475,11 +482,13 @@ * @method refresh */ refresh: function () { - + var prop; - + for (prop in this.config) { - this.refireEvent(prop); + if (Lang.hasOwnProperty(this.config, prop)) { + this.refireEvent(prop); + } } }, @@ -503,9 +512,14 @@ key = queueItem[0]; value = queueItem[1]; property = this.config[key]; - + property.value = value; - + + // Clear out queue entry, to avoid it being + // re-added to the queue by any queueProperty/supercedes + // calls which are invoked during fireEvent + this.eventQueue[i] = null; + this.fireEvent(key,value); } } @@ -533,19 +547,12 @@ 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; - } }, @@ -665,32 +672,23 @@ 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. @@ -742,6 +740,19 @@ * @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. @@ -758,7 +769,6 @@ var newMonth = date.getMonth() + amount; var years = 0; - if (newMonth < 0) { while (newMonth < 0) { newMonth += 12; @@ -770,24 +780,56 @@ years += 1; } } - + d.setMonth(newMonth); d.setFullYear(date.getFullYear() + years); break; case this.DAY: - d.setDate(date.getDate() + amount); + this._addDays(d, amount); + // d.setDate(date.getDate() + amount); break; case this.YEAR: d.setFullYear(date.getFullYear() + amount); break; case this.WEEK: - d.setDate(date.getDate() + (amount * 7)); + 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 @@ -854,7 +896,7 @@ * @return {Date} January 1 of the calendar year specified. */ getJan1 : function(calendarYear) { - return new Date(calendarYear,0,1); + return this.getDate(calendarYear,0,1); }, /** @@ -874,30 +916,79 @@ }, /** - * Calculates the week number for the given date. This function assumes that week 1 is the - * week in which January 1 appears, regardless of whether the week consists of a full 7 days. - * The calendar year can be specified to help find what a the week number would be for a given - * date if the date overlaps years. For instance, a week may be considered week 1 of 2005, or - * week 53 of 2004. Specifying the optional calendarYear allows one to make this distinction - * easily. + * 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} calendarYear OPTIONAL - The calendar year to use for determining the week number. Default is - * the calendar year of parameter "date". - * @return {Number} The week number of the given date. + * @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, calendarYear) { - date = this.clearTime(date); - var nearestThurs = new Date(date.getTime() + (4 * this.ONE_DAY_MS) - ((date.getDay()) * this.ONE_DAY_MS)); + getWeekNumber : function(date, firstDayOfWeek, janDate) { - var jan1 = new Date(nearestThurs.getFullYear(),0,1); - var dayOfYear = ((nearestThurs.getTime() - jan1.getTime()) / this.ONE_DAY_MS) - 1; + // Setup Defaults + firstDayOfWeek = firstDayOfWeek || 0; + janDate = janDate || this.WEEK_ONE_JAN_DATE; - var weekNum = Math.ceil((dayOfYear)/ 7); + 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. @@ -934,7 +1025,7 @@ * @return {Date} The JavaScript Date representing the first day of the month */ findMonthStart : function(date) { - var start = new Date(date.getFullYear(), date.getMonth(), 1); + var start = this.getDate(date.getFullYear(), date.getMonth(), 1); return start; }, @@ -960,17 +1051,55 @@ 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. +* 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 +* @title Calendar +* @namespace YAHOO.widget * @requires yahoo,dom,event */ +(function(){ + var Dom = YAHOO.util.Dom, + Event = YAHOO.util.Event, + Lang = YAHOO.lang, + DateMath = YAHOO.widget.DateMath; + /** * 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 @@ -979,19 +1108,40 @@ *

To construct the placeholder for the calendar widget, the code is as * follows: *

-* <div id="cal1Container"></div> +* <div id="calContainer"></div> * *

+*

+* 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 The id of the table element that will represent the calendar widget -* @param {String} containerId The id of the container div element that will wrap the calendar table -* @param {Object} config The configuration object containing the Calendar's arguments +* @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(id, containerId, config); -}; +function Calendar(id, containerId, config) { + this.init.apply(this, arguments); +} /** * The path to be used for images loaded for the Calendar @@ -1000,7 +1150,7 @@ * @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; +Calendar.IMG_ROOT = null; /** * Type constant used for renderers to represent an individual date (M/D/Y) @@ -1009,7 +1159,7 @@ * @final * @type String */ -YAHOO.widget.Calendar.DATE = "D"; +Calendar.DATE = "D"; /** * Type constant used for renderers to represent an individual date across any year (M/D) @@ -1018,7 +1168,7 @@ * @final * @type String */ -YAHOO.widget.Calendar.MONTH_DAY = "MD"; +Calendar.MONTH_DAY = "MD"; /** * Type constant used for renderers to represent a weekday @@ -1027,7 +1177,7 @@ * @final * @type String */ -YAHOO.widget.Calendar.WEEKDAY = "WD"; +Calendar.WEEKDAY = "WD"; /** * Type constant used for renderers to represent a range of individual dates (M/D/Y-M/D/Y) @@ -1036,7 +1186,7 @@ * @final * @type String */ -YAHOO.widget.Calendar.RANGE = "R"; +Calendar.RANGE = "R"; /** * Type constant used for renderers to represent a month across any year @@ -1045,7 +1195,7 @@ * @final * @type String */ -YAHOO.widget.Calendar.MONTH = "M"; +Calendar.MONTH = "M"; /** * Constant that represents the total number of date cells that are displayed in a given month @@ -1054,7 +1204,7 @@ * @final * @type Number */ -YAHOO.widget.Calendar.DISPLAY_DAYS = 42; +Calendar.DISPLAY_DAYS = 42; /** * Constant used for halting the execution of the remainder of the render stack @@ -1063,7 +1213,7 @@ * @final * @type String */ -YAHOO.widget.Calendar.STOP_RENDER = "S"; +Calendar.STOP_RENDER = "S"; /** * Constant used to represent short date field string formats (e.g. Tu or Feb) @@ -1072,7 +1222,7 @@ * @final * @type String */ -YAHOO.widget.Calendar.SHORT = "short"; +Calendar.SHORT = "short"; /** * Constant used to represent long date field string formats (e.g. Monday or February) @@ -1081,7 +1231,7 @@ * @final * @type String */ -YAHOO.widget.Calendar.LONG = "long"; +Calendar.LONG = "long"; /** * Constant used to represent medium date field string formats (e.g. Mon) @@ -1090,7 +1240,7 @@ * @final * @type String */ -YAHOO.widget.Calendar.MEDIUM = "medium"; +Calendar.MEDIUM = "medium"; /** * Constant used to represent single character date field string formats (e.g. M, T, W) @@ -1099,7 +1249,7 @@ * @final * @type String */ -YAHOO.widget.Calendar.ONE_CHAR = "1char"; +Calendar.ONE_CHAR = "1char"; /** * The set of default Config property keys and values for the Calendar @@ -1109,7 +1259,7 @@ * @private * @type Object */ -YAHOO.widget.Calendar._DEFAULT_CONFIG = { +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}, @@ -1147,9 +1297,21 @@ 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:""} + MY_LABEL_YEAR_SUFFIX:{key:"my_label_year_suffix", value:""}, + NAV: {key:"navigator", value: null}, + STRINGS : { + key:"strings", + value: { + previousMonth : "Previous Month", + nextMonth : "Next Month", + close: "Close" + }, + supercedes : ["close", "title"] + } }; +var DEF_CFG = Calendar._DEFAULT_CONFIG; + /** * The set of Custom Event types supported by the Calendar * @property YAHOO.widget.Calendar._EVENT_TYPES @@ -1158,16 +1320,28 @@ * @private * @type Object */ -YAHOO.widget.Calendar._EVENT_TYPES = { +Calendar._EVENT_TYPES = { BEFORE_SELECT : "beforeSelect", SELECT : "select", BEFORE_DESELECT : "beforeDeselect", DESELECT : "deselect", CHANGE_PAGE : "changePage", BEFORE_RENDER : "beforeRender", RENDER : "render", + BEFORE_DESTROY : "beforeDestroy", + DESTROY : "destroy", RESET : "reset", - CLEAR : "clear" + 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" }; /** @@ -1178,7 +1352,7 @@ * @private * @type Object */ -YAHOO.widget.Calendar._STYLES = { +Calendar._STYLES = { CSS_ROW_HEADER: "calrowhead", CSS_ROW_FOOTER: "calrowfoot", CSS_CELL : "calcell", @@ -1200,6 +1374,7 @@ 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", @@ -1212,7 +1387,7 @@ CSS_CELL_HIGHLIGHT4 : "highlight4" }; -YAHOO.widget.Calendar.prototype = { +Calendar.prototype = { /** * The configuration object used to set up the calendars various locale and style options. @@ -1243,7 +1418,7 @@ * @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 @@ -1252,13 +1427,20 @@ cellDates : null, /** - * The id that uniquely identifies this calendar. This id should match the id of the placeholder element on the page. + * 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 @@ -1288,6 +1470,14 @@ _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 @@ -1300,3652 +1490,5659 @@ * @property domEventMap * @type Object */ - domEventMap : null -}; + domEventMap : null, - - -/** -* Initializes the Calendar widget. -* @method init -* @param {String} id The id of the table element that will represent the calendar widget -* @param {String} containerId The id of the container div element that will wrap the calendar table -* @param {Object} config The configuration object containing the Calendar's arguments -*/ -YAHOO.widget.Calendar.prototype.init = function(id, containerId, config) { - this.initEvents(); - this.today = new Date(); - YAHOO.widget.DateMath.clearTime(this.today); - - this.id = id; - this.oDomContainer = document.getElementById(containerId); - /** - * 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 = {}; + * 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 - /** - * The local object which contains the Calendar's locale settings - * @property Locale - * @type Object - */ - this.Locale = {}; + new Calendar(String) + new Calendar(HTMLElement) + new Calendar(String, ConfigObject) + new Calendar(HTMLElement, ConfigObject) - this.initStyles(); + Pre 2.4.0 Constructor signatures - 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 = []; + 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}; - 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 -*/ -YAHOO.widget.Calendar.prototype.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); + 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 (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; } - } - } else { - if (this.iframe) { - if (this.iframe.parentNode) { - this.iframe.parentNode.removeChild(this.iframe); - } - this.iframe = null; - } + break; + default: // 3+ + nArgs.id = args[0]; + nArgs.container = args[1]; + nArgs.config = args[2]; + break; } - } - } -}; - -/** -* Default handler for the "title" property -* @method configTitle -*/ -YAHOO.widget.Calendar.prototype.configTitle = function(type, args, obj) { - var title = args[0]; - var close = this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.CLOSE.key); - - var titleDiv; - - if (title && title !== "") { - titleDiv = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE, "div", this.oDomContainer)[0] || document.createElement("div"); - titleDiv.className = YAHOO.widget.CalendarGroup.CSS_2UPTITLE; - titleDiv.innerHTML = title; - this.oDomContainer.insertBefore(titleDiv, this.oDomContainer.firstChild); - YAHOO.util.Dom.addClass(this.oDomContainer, "withtitle"); - } else { - titleDiv = YAHOO.util.Dom.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE, "div", this.oDomContainer)[0] || null; - - if (titleDiv) { - YAHOO.util.Event.purgeElement(titleDiv); - this.oDomContainer.removeChild(titleDiv); - } - if (! close) { - YAHOO.util.Dom.removeClass(this.oDomContainer, "withtitle"); - } - } -}; - -/** -* Default handler for the "close" property -* @method configClose -*/ -YAHOO.widget.Calendar.prototype.configClose = function(type, args, obj) { - var close = args[0]; - var title = this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.TITLE.key); - - var DEPR_CLOSE_PATH = "us/my/bn/x_d.gif"; - - var linkClose; - - if (close === true) { - linkClose = YAHOO.util.Dom.getElementsByClassName("link-close", "a", this.oDomContainer)[0] || document.createElement("a"); - linkClose.href = "#"; - linkClose.className = "link-close"; - YAHOO.util.Event.addListener(linkClose, "click", function(e, cal) {cal.hide(); YAHOO.util.Event.preventDefault(e); }, this); - - if (YAHOO.widget.Calendar.IMG_ROOT !== null) { - var imgClose = document.createElement("img"); - imgClose.src = YAHOO.widget.Calendar.IMG_ROOT + DEPR_CLOSE_PATH; - imgClose.className = YAHOO.widget.CalendarGroup.CSS_2UPCLOSE; - linkClose.appendChild(imgClose); } else { - linkClose.innerHTML = ''; } - - this.oDomContainer.appendChild(linkClose); - YAHOO.util.Dom.addClass(this.oDomContainer, "withtitle"); - } else { - linkClose = YAHOO.util.Dom.getElementsByClassName("link-close", "a", this.oDomContainer)[0] || null; - if (linkClose) { - YAHOO.util.Event.purgeElement(linkClose); - this.oDomContainer.removeChild(linkClose); - } - if (! title || title === "") { - YAHOO.util.Dom.removeClass(this.oDomContainer, "withtitle"); - } - } -}; + return nArgs; + }, -/** -* Initializes Calendar's built-in CustomEvents -* @method initEvents -*/ -YAHOO.widget.Calendar.prototype.initEvents = function() { - - var defEvents = YAHOO.widget.Calendar._EVENT_TYPES; - /** - * Fired before a selection is made - * @event beforeSelectEvent + * 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. */ - this.beforeSelectEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_SELECT); + init : function(id, container, config) { + // Normalize 2.4.0, pre 2.4.0 args + var nArgs = this._parseArgs(arguments); - /** - * 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); + id = nArgs.id; + container = nArgs.container; + config = nArgs.config; - /** - * Fired before a selection is made - * @event beforeDeselectEvent - */ - this.beforeDeselectEvent = new YAHOO.util.CustomEvent(defEvents.BEFORE_DESELECT); + this.oDomContainer = Dom.get(container); - /** - * 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); - - 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 -*/ -YAHOO.widget.Calendar.prototype.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; + if (!this.oDomContainer.id) { + this.oDomContainer.id = Dom.generateId(); } - - target = target.parentNode; - tagName = target.tagName.toLowerCase(); - if (tagName == "html") { - return; + if (!id) { + id = this.oDomContainer.id + "_t"; } - } - if (defSelector) { - // Stop link href navigation for default renderer - YAHOO.util.Event.preventDefault(e); - } + this.id = id; + this.containerId = this.oDomContainer.id; - cell = target; + this.initEvents(); - if (YAHOO.util.Dom.hasClass(cell, cal.Style.CSS_CELL_SELECTABLE)) { - index = cell.id.split("cell")[1]; - d = cal.cellDates[index]; - date = new Date(d[0],d[1]-1,d[2]); - - var link; + this.today = new Date(); + DateMath.clearTime(this.today); - if (cal.Options.MULTI_SELECT) { - link = cell.getElementsByTagName("a")[0]; - if (link) { - link.blur(); - } + /** + * 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); - var cellDate = cal.cellDates[index]; - var cellDateIndex = cal._indexOfSelectedFieldArray(cellDate); + /** + * The local object which contains the Calendar's options + * @property Options + * @type Object + */ + this.Options = {}; - if (cellDateIndex > -1) { - cal.deselectCell(index); - } else { - cal.selectCell(index); - } + /** + * The local object which contains the Calendar's locale settings + * @property Locale + * @type Object + */ + this.Locale = {}; - } else { - link = cell.getElementsByTagName("a")[0]; - if (link) { - link.blur(); - } - cal.selectCell(index); - } - } -}; + this.initStyles(); -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.doCellMouseOver = function(e, cal) { - var target; - if (e) { - target = YAHOO.util.Event.getTarget(e); - } else { - target = this; - } + Dom.addClass(this.oDomContainer, this.Style.CSS_CONTAINER); + Dom.addClass(this.oDomContainer, this.Style.CSS_SINGLE); - while (target.tagName.toLowerCase() != "td") { - target = target.parentNode; - if (target.tagName.toLowerCase() == "html") { - return; - } - } + this.cellDates = []; + this.cells = []; + this.renderStack = []; + this._renderStack = []; - if (YAHOO.util.Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) { - YAHOO.util.Dom.addClass(target, cal.Style.CSS_CELL_HOVER); - } -}; + this.setupConfig(); -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.doCellMouseOut = function(e, cal) { - var target; - if (e) { - target = YAHOO.util.Event.getTarget(e); - } else { - target = this; - } - - while (target.tagName.toLowerCase() != "td") { - target = target.parentNode; - if (target.tagName.toLowerCase() == "html") { - return; + if (config) { + this.cfg.applyConfig(config, true); } - } - if (YAHOO.util.Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) { - YAHOO.util.Dom.removeClass(target, cal.Style.CSS_CELL_HOVER); - } -}; + this.cfg.fireQueue(); + }, -YAHOO.widget.Calendar.prototype.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 + * 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 */ - this.cfg.addProperty(defCfg.PAGEDATE.key, { value:new Date(), handler:this.configPageDate } ); + configIframe : function(type, args, obj) { + var useIframe = args[0]; + + if (!this.parent) { + if (Dom.inDocument(this.oDomContainer)) { + if (useIframe) { + var pos = Dom.getStyle(this.oDomContainer, "position"); + + if (pos == "absolute" || pos == "relative") { + + if (!Dom.inDocument(this.iframe)) { + this.iframe = document.createElement("iframe"); + this.iframe.src = "javascript:false;"; + + Dom.setStyle(this.iframe, "opacity", "0"); + + if (YAHOO.env.ua.ie && YAHOO.env.ua.ie <= 6) { + 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; + } + } + } + } + }, /** - * The date or range of dates representing the current Calendar selection - * @config selected - * @type String - * @default [] + * Default handler for the "title" property + * @method configTitle */ - this.cfg.addProperty(defCfg.SELECTED.key, { value:[], handler:this.configSelected } ); + configTitle : function(type, args, obj) { + var title = args[0]; + // "" disables title bar + if (title) { + this.createTitleBar(title); + } else { + var close = this.cfg.getProperty(DEF_CFG.CLOSE.key); + if (!close) { + this.removeTitleBar(); + } else { + this.createTitleBar(" "); + } + } + }, + /** - * The title to display above the Calendar's month header - * @config title - * @type String - * @default "" + * Default handler for the "close" property + * @method configClose */ - this.cfg.addProperty(defCfg.TITLE.key, { value:defCfg.TITLE.value, handler:this.configTitle } ); + configClose : function(type, args, obj) { + var close = args[0], + title = this.cfg.getProperty(DEF_CFG.TITLE.key); + + if (close) { + if (!title) { + this.createTitleBar(" "); + } + this.createCloseButton(); + } else { + this.removeCloseButton(); + if (!title) { + this.removeTitleBar(); + } + } + }, /** - * Whether or not a close button should be displayed for this Calendar - * @config close - * @type Boolean - * @default false + * Initializes Calendar's built-in CustomEvents + * @method initEvents */ - this.cfg.addProperty(defCfg.CLOSE.key, { value:defCfg.CLOSE.value, handler:this.configClose } ); + initEvents : function() { - /** - * 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 } ); + var defEvents = Calendar._EVENT_TYPES, + CE = YAHOO.util.CustomEvent, + cal = this; // To help with minification - /** - * 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 } ); + /** + * Fired before a date selection is made + * @event beforeSelectEvent + */ + cal.beforeSelectEvent = new CE(defEvents.BEFORE_SELECT); - /** - * 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 } ); + /** + * Fired when a date selection is made + * @event selectEvent + * @param {Array} Array of Date field arrays in the format [YYYY, MM, DD]. + */ + cal.selectEvent = new CE(defEvents.SELECT); + /** + * Fired before a date or set of dates is deselected + * @event beforeDeselectEvent + */ + cal.beforeDeselectEvent = new CE(defEvents.BEFORE_DESELECT); - // Options properties + /** + * Fired when a date or set of dates is deselected + * @event deselectEvent + * @param {Array} Array of Date field arrays in the format [YYYY, MM, DD]. + */ + cal.deselectEvent = new CE(defEvents.DESELECT); + + /** + * Fired when the Calendar page is changed + * @event changePageEvent + */ + cal.changePageEvent = new CE(defEvents.CHANGE_PAGE); + + /** + * Fired before the Calendar is rendered + * @event beforeRenderEvent + */ + cal.beforeRenderEvent = new CE(defEvents.BEFORE_RENDER); + + /** + * Fired when the Calendar is rendered + * @event renderEvent + */ + cal.renderEvent = new CE(defEvents.RENDER); - /** - * 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 } ); + /** + * Fired just before the Calendar is to be destroyed + * @event beforeDestroyEvent + */ + cal.beforeDestroyEvent = new CE(defEvents.BEFORE_DESTROY); - /** - * 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.configOptions, validator:this.cfg.checkNumber } ); + /** + * Fired after the Calendar is destroyed. This event should be used + * for notification only. When this event is fired, important Calendar instance + * properties, dom references and event listeners have already been + * removed/dereferenced, and hence the Calendar instance is not in a usable + * state. + * + * @event destroyEvent + */ + cal.destroyEvent = new CE(defEvents.DESTROY); - /** - * 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 } ); + /** + * Fired when the Calendar is reset + * @event resetEvent + */ + cal.resetEvent = new CE(defEvents.RESET); - /** - * 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 } ); + /** + * Fired when the Calendar is cleared + * @event clearEvent + */ + cal.clearEvent = new CE(defEvents.CLEAR); - /** - * 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 } ); + /** + * Fired just before the Calendar is to be shown + * @event beforeShowEvent + */ + cal.beforeShowEvent = new CE(defEvents.BEFORE_SHOW); - /** - * 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 } ); + /** + * Fired after the Calendar is shown + * @event showEvent + */ + cal.showEvent = new CE(defEvents.SHOW); - /** - * 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 } ); + /** + * Fired just before the Calendar is to be hidden + * @event beforeHideEvent + */ + cal.beforeHideEvent = new CE(defEvents.BEFORE_HIDE); - // Locale properties + /** + * Fired after the Calendar is hidden + * @event hideEvent + */ + cal.hideEvent = new CE(defEvents.HIDE); - /** - * 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 } ); + /** + * Fired just before the CalendarNavigator is to be shown + * @event beforeShowNavEvent + */ + cal.beforeShowNavEvent = new CE(defEvents.BEFORE_SHOW_NAV); - /** - * 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 } ); + /** + * Fired after the CalendarNavigator is shown + * @event showNavEvent + */ + cal.showNavEvent = new CE(defEvents.SHOW_NAV); - /** - * 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 } ); + /** + * Fired just before the CalendarNavigator is to be hidden + * @event beforeHideNavEvent + */ + cal.beforeHideNavEvent = new CE(defEvents.BEFORE_HIDE_NAV); - /** - * 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 } ); + /** + * Fired after the CalendarNavigator is hidden + * @event hideNavEvent + */ + cal.hideNavEvent = new CE(defEvents.HIDE_NAV); - /** - * 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); - }; + /** + * Fired just before the CalendarNavigator is to be rendered + * @event beforeRenderNavEvent + */ + cal.beforeRenderNavEvent = new CE(defEvents.BEFORE_RENDER_NAV); - 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 } ); + /** + * Fired after the CalendarNavigator is rendered + * @event renderNavEvent + */ + cal.renderNavEvent = new CE(defEvents.RENDER_NAV); - /** - * 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 } ); + cal.beforeSelectEvent.subscribe(cal.onBeforeSelect, this, true); + cal.selectEvent.subscribe(cal.onSelect, this, true); + cal.beforeDeselectEvent.subscribe(cal.onBeforeDeselect, this, true); + cal.deselectEvent.subscribe(cal.onDeselect, this, true); + cal.changePageEvent.subscribe(cal.onChangePage, this, true); + cal.renderEvent.subscribe(cal.onRender, this, true); + cal.resetEvent.subscribe(cal.onReset, this, true); + cal.clearEvent.subscribe(cal.onClear, this, true); + }, /** - * 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 "-" + * The default event handler for clicks on the "Previous Month" navigation UI + * + * @method doPreviousMonthNav + * @param {DOMEvent} e The DOM event + * @param {Calendar} cal A reference to the calendar */ - this.cfg.addProperty(defCfg.DATE_RANGE_DELIMITER.key, { value:defCfg.DATE_RANGE_DELIMITER.value, handler:this.configLocale } ); + doPreviousMonthNav : function(e, cal) { + Event.preventDefault(e); + // previousMonth invoked in a timeout, to allow + // event to bubble up, with correct target. Calling + // previousMonth, will call render which will remove + // HTML which generated the event, resulting in an + // invalid event target in certain browsers. + setTimeout(function() { + cal.previousMonth(); + var navs = Dom.getElementsByClassName(cal.Style.CSS_NAV_LEFT, "a", cal.oDomContainer); + if (navs && navs[0]) { + try { + navs[0].focus(); + } catch (e) { + // ignore + } + } + }, 0); + }, /** - * 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 default event handler for clicks on the "Next Month" navigation UI + * + * @method doNextMonthNav + * @param {DOMEvent} e The DOM event + * @param {Calendar} cal A reference to the calendar + */ + doNextMonthNav : function(e, cal) { + Event.preventDefault(e); + setTimeout(function() { + cal.nextMonth(); + var navs = Dom.getElementsByClassName(cal.Style.CSS_NAV_RIGHT, "a", cal.oDomContainer); + if (navs && navs[0]) { + try { + navs[0].focus(); + } catch (e) { + // ignore + } + } + }, 0); + }, /** - * The position of the year in a month/year date string - * @config MY_YEAR_POSITION - * @type Number - * @default 2 + * The default event handler for date cell selection. Currently attached to + * the Calendar's bounding box, referenced by it's oDomContainer property. + * + * @method doSelectCell + * @param {DOMEvent} e The DOM event + * @param {Calendar} cal A reference to the calendar */ - this.cfg.addProperty(defCfg.MY_YEAR_POSITION.key, { value:defCfg.MY_YEAR_POSITION.value, handler:this.configLocale, validator:this.cfg.checkNumber } ); + doSelectCell : function(e, cal) { + var cell, d, date, index; - /** - * 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 } ); + var target = Event.getTarget(e), + tagName = target.tagName.toLowerCase(), + defSelector = false; - /** - * 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 } ); + while (tagName != "td" && !Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) { - /** - * 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 } ); + if (!defSelector && tagName == "a" && Dom.hasClass(target, cal.Style.CSS_CELL_SELECTOR)) { + defSelector = true; + } - /** - * 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 } ); + target = target.parentNode; + tagName = target.tagName.toLowerCase(); - /** - * 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 } ); + if (target == this.oDomContainer || tagName == "html") { + return; + } + } - /** - * 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 } ); + if (defSelector) { + // Stop link href navigation for default renderer + Event.preventDefault(e); + } - /** - * 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 } ); -}; + cell = target; -/** -* The default handler for the "pagedate" property -* @method configPageDate -*/ -YAHOO.widget.Calendar.prototype.configPageDate = function(type, args, obj) { - this.cfg.setProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key, this._parsePageDate(args[0]), true); -}; + if (Dom.hasClass(cell, cal.Style.CSS_CELL_SELECTABLE)) { + index = cal.getIndexFromId(cell.id); + if (index > -1) { + d = cal.cellDates[index]; + if (d) { + date = DateMath.getDate(d[0],d[1]-1,d[2]); + + var link; -/** -* The default handler for the "mindate" property -* @method configMinDate -*/ -YAHOO.widget.Calendar.prototype.configMinDate = function(type, args, obj) { - var val = args[0]; - if (YAHOO.lang.isString(val)) { - val = this._parseDate(val); - this.cfg.setProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.MINDATE.key, new Date(val[0],(val[1]-1),val[2])); - } -}; + if (cal.Options.MULTI_SELECT) { + link = cell.getElementsByTagName("a")[0]; + if (link) { + link.blur(); + } -/** -* The default handler for the "maxdate" property -* @method configMaxDate -*/ -YAHOO.widget.Calendar.prototype.configMaxDate = function(type, args, obj) { - var val = args[0]; - if (YAHOO.lang.isString(val)) { - val = this._parseDate(val); - this.cfg.setProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.MAXDATE.key, new Date(val[0],(val[1]-1),val[2])); - } -}; + var cellDate = cal.cellDates[index]; + var cellDateIndex = cal._indexOfSelectedFieldArray(cellDate); -/** -* The default handler for the "selected" property -* @method configSelected -*/ -YAHOO.widget.Calendar.prototype.configSelected = function(type, args, obj) { - var selected = args[0]; - var cfgSelected = YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key; - - if (selected) { - if (YAHOO.lang.isString(selected)) { - this.cfg.setProperty(cfgSelected, this._parseDates(selected), true); - } - } - if (! this._selectedDates) { - this._selectedDates = this.cfg.getProperty(cfgSelected); - } -}; + if (cellDateIndex > -1) { + cal.deselectCell(index); + } else { + cal.selectCell(index); + } -/** -* The default handler for all configuration options properties -* @method configOptions -*/ -YAHOO.widget.Calendar.prototype.configOptions = function(type, args, obj) { - this.Options[type.toUpperCase()] = args[0]; -}; + } else { + link = cell.getElementsByTagName("a")[0]; + if (link) { + link.blur(); + } + cal.selectCell(index); + } + } + } + } + }, -/** -* The default handler for all configuration locale properties -* @method configLocale -*/ -YAHOO.widget.Calendar.prototype.configLocale = function(type, args, obj) { - var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG; - this.Locale[type.toUpperCase()] = args[0]; + /** + * 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 = Event.getTarget(e); + } else { + target = this; + } - this.cfg.refireEvent(defCfg.LOCALE_MONTHS.key); - this.cfg.refireEvent(defCfg.LOCALE_WEEKDAYS.key); -}; + while (target.tagName && target.tagName.toLowerCase() != "td") { + target = target.parentNode; + if (!target.tagName || target.tagName.toLowerCase() == "html") { + return; + } + } -/** -* The default handler for all configuration locale field length properties -* @method configLocaleValues -*/ -YAHOO.widget.Calendar.prototype.configLocaleValues = function(type, args, obj) { - var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG; + if (Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) { + Dom.addClass(target, cal.Style.CSS_CELL_HOVER); + } + }, - type = type.toLowerCase(); - var val = args[0]; + /** + * 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 = Event.getTarget(e); + } else { + target = this; + } - switch (type) { - case defCfg.LOCALE_MONTHS.key: - switch (val) { - case YAHOO.widget.Calendar.SHORT: - this.Locale.LOCALE_MONTHS = this.cfg.getProperty(defCfg.MONTHS_SHORT.key).concat(); - break; - case YAHOO.widget.Calendar.LONG: - this.Locale.LOCALE_MONTHS = this.cfg.getProperty(defCfg.MONTHS_LONG.key).concat(); - break; + while (target.tagName && target.tagName.toLowerCase() != "td") { + target = target.parentNode; + if (!target.tagName || target.tagName.toLowerCase() == "html") { + return; } - break; - case defCfg.LOCALE_WEEKDAYS.key: - switch (val) { - case YAHOO.widget.Calendar.ONE_CHAR: - this.Locale.LOCALE_WEEKDAYS = this.cfg.getProperty(defCfg.WEEKDAYS_1CHAR.key).concat(); - break; - case YAHOO.widget.Calendar.SHORT: - this.Locale.LOCALE_WEEKDAYS = this.cfg.getProperty(defCfg.WEEKDAYS_SHORT.key).concat(); - break; - case YAHOO.widget.Calendar.MEDIUM: - this.Locale.LOCALE_WEEKDAYS = this.cfg.getProperty(defCfg.WEEKDAYS_MEDIUM.key).concat(); - break; - case YAHOO.widget.Calendar.LONG: - this.Locale.LOCALE_WEEKDAYS = this.cfg.getProperty(defCfg.WEEKDAYS_LONG.key).concat(); - break; - } - - var START_WEEKDAY = this.cfg.getProperty(defCfg.START_WEEKDAY.key); + } - if (START_WEEKDAY > 0) { - for (var w=0;w + * 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"
+ *
+ *

E.g.

+ *
+		* var navConfig = {
+		*	  strings: {
+		*		  month:"Calendar Month",
+		*		  year:"Calendar Year",
+		*		  submit: "Submit",
+		*		  cancel: "Cancel",
+		*		  invalidYear: "Please enter a valid year"
+		*	  },
+		*	  monthFormat: YAHOO.widget.Calendar.SHORT,
+		*	  initialFocus: "month"
+		* }
+		* 
+ * @config navigator + * @type {Object|Boolean} + * @default null */ - CSS_CELL_HIGHLIGHT4 : defStyle.CSS_CELL_HIGHLIGHT4 - }; -}; + cfg.addProperty(DEF_CFG.NAV.key, { value:DEF_CFG.NAV.value, handler:this.configNavigator } ); -/** -* Builds the date label that will be displayed in the calendar header or -* footer, depending on configuration. -* @method buildMonthLabel -* @return {String} The formatted calendar month label -*/ -YAHOO.widget.Calendar.prototype.buildMonthLabel = function() { - var pageDate = this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.PAGEDATE.key); + /** + * The map of UI strings which the Calendar UI uses. + * + * @config strings + * @type {Object} + * @default An object with the properties shown below: + *
+ *
previousMonth
String : The string to use for the "Previous Month" navigation UI. Defaults to "Previous Month".
+ *
nextMonth
String : The string to use for the "Next Month" navigation UI. Defaults to "Next Month".
+ *
close
String : The string to use for the close button label. Defaults to "Close".
+ *
+ */ + cfg.addProperty(DEF_CFG.STRINGS.key, { + value:DEF_CFG.STRINGS.value, + handler:this.configStrings, + validator: function(val) { + return Lang.isObject(val); + }, + supercedes:DEF_CFG.STRINGS.supercedes + }); + }, - var monthLabel = this.Locale.LOCALE_MONTHS[pageDate.getMonth()] + this.Locale.MY_LABEL_MONTH_SUFFIX; - var yearLabel = pageDate.getFullYear() + this.Locale.MY_LABEL_YEAR_SUFFIX; + /** + * The default handler for the "strings" property + * @method configStrings + */ + configStrings : function(type, args, obj) { + var val = Lang.merge(DEF_CFG.STRINGS.value, args[0]); + this.cfg.setProperty(DEF_CFG.STRINGS.key, val, true); + }, - if (this.Locale.MY_LABEL_MONTH_POSITION == 2 || this.Locale.MY_LABEL_YEAR_POSITION == 1) { - return yearLabel + monthLabel; - } else { - return monthLabel + yearLabel; - } -}; + /** + * The default handler for the "pagedate" property + * @method configPageDate + */ + configPageDate : function(type, args, obj) { + this.cfg.setProperty(DEF_CFG.PAGEDATE.key, this._parsePageDate(args[0]), true); + }, -/** -* Builds the date digit that will be displayed in calendar cells -* @method buildDayLabel -* @param {Date} workingDate The current working date -* @return {String} The formatted day label -*/ -YAHOO.widget.Calendar.prototype.buildDayLabel = function(workingDate) { - return workingDate.getDate(); -}; + /** + * The default handler for the "mindate" property + * @method configMinDate + */ + configMinDate : function(type, args, obj) { + var val = args[0]; + if (Lang.isString(val)) { + val = this._parseDate(val); + this.cfg.setProperty(DEF_CFG.MINDATE.key, DateMath.getDate(val[0],(val[1]-1),val[2])); + } + }, -/** -* Renders the calendar header. -* @method renderHeader -* @param {Array} html The current working HTML array -* @return {Array} The current working HTML array -*/ -YAHOO.widget.Calendar.prototype.renderHeader = function(html) { - var colSpan = 7; - - var DEPR_NAV_LEFT = "us/tr/callt.gif"; - var DEPR_NAV_RIGHT = "us/tr/calrt.gif"; - var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG; - - if (this.cfg.getProperty(defCfg.SHOW_WEEK_HEADER.key)) { - colSpan += 1; - } - - if (this.cfg.getProperty(defCfg.SHOW_WEEK_FOOTER.key)) { - colSpan += 1; - } - - html[html.length] = ""; - html[html.length] = ""; - html[html.length] = ''; - html[html.length] = '
'; - - var renderLeft, renderRight = false; - - if (this.parent) { - if (this.index === 0) { - renderLeft = true; + /** + * The default handler for the "maxdate" property + * @method configMaxDate + */ + configMaxDate : function(type, args, obj) { + var val = args[0]; + if (Lang.isString(val)) { + val = this._parseDate(val); + this.cfg.setProperty(DEF_CFG.MAXDATE.key, DateMath.getDate(val[0],(val[1]-1),val[2])); } - if (this.index == (this.parent.cfg.getProperty("pages") -1)) { - renderRight = true; - } - } else { - renderLeft = true; - renderRight = true; - } + }, - var cal = this.parent || this; - - if (renderLeft) { - var leftArrow = this.cfg.getProperty(defCfg.NAV_ARROW_LEFT.key); - // Check for deprecated customization - If someone set IMG_ROOT, but didn't set NAV_ARROW_LEFT, then set NAV_ARROW_LEFT to the old deprecated value - if (leftArrow === null && YAHOO.widget.Calendar.IMG_ROOT !== null) { - leftArrow = YAHOO.widget.Calendar.IMG_ROOT + DEPR_NAV_LEFT; + /** + * The default handler for the "selected" property + * @method configSelected + */ + configSelected : function(type, args, obj) { + var selected = args[0], + cfgSelected = DEF_CFG.SELECTED.key; + + if (selected) { + if (Lang.isString(selected)) { + this.cfg.setProperty(cfgSelected, this._parseDates(selected), true); + } } - var leftStyle = (leftArrow === null) ? "" : ' style="background-image:url(' + leftArrow + ')"'; - html[html.length] = ' '; - } - - html[html.length] = this.buildMonthLabel(); - - if (renderRight) { - var rightArrow = this.cfg.getProperty(defCfg.NAV_ARROW_RIGHT.key); - if (rightArrow === null && YAHOO.widget.Calendar.IMG_ROOT !== null) { - rightArrow = YAHOO.widget.Calendar.IMG_ROOT + DEPR_NAV_RIGHT; + if (! this._selectedDates) { + this._selectedDates = this.cfg.getProperty(cfgSelected); } - var rightStyle = (rightArrow === null) ? "" : ' style="background-image:url(' + rightArrow + ')"'; - html[html.length] = ' '; - } + }, + + /** + * The default handler for all configuration options properties + * @method configOptions + */ + configOptions : function(type, args, obj) { + this.Options[type.toUpperCase()] = args[0]; + }, - html[html.length] = '
\n\n'; + /** + * The default handler for all configuration locale properties + * @method configLocale + */ + configLocale : function(type, args, obj) { + this.Locale[type.toUpperCase()] = args[0]; - if (this.cfg.getProperty(defCfg.SHOW_WEEKDAYS.key)) { - html = this.buildWeekdays(html); - } + this.cfg.refireEvent(DEF_CFG.LOCALE_MONTHS.key); + this.cfg.refireEvent(DEF_CFG.LOCALE_WEEKDAYS.key); + }, - html[html.length] = ''; + /** + * The default handler for all configuration locale field length properties + * @method configLocaleValues + */ + configLocaleValues : function(type, args, obj) { - return html; -}; + type = type.toLowerCase(); -/** -* Renders the Calendar's weekday headers. -* @method buildWeekdays -* @param {Array} html The current working HTML array -* @return {Array} The current working HTML array -*/ -YAHOO.widget.Calendar.prototype.buildWeekdays = function(html) { + var val = args[0], + cfg = this.cfg, + Locale = this.Locale; - var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG; + switch (type) { + case DEF_CFG.LOCALE_MONTHS.key: + switch (val) { + case Calendar.SHORT: + Locale.LOCALE_MONTHS = cfg.getProperty(DEF_CFG.MONTHS_SHORT.key).concat(); + break; + case Calendar.LONG: + Locale.LOCALE_MONTHS = cfg.getProperty(DEF_CFG.MONTHS_LONG.key).concat(); + break; + } + break; + case DEF_CFG.LOCALE_WEEKDAYS.key: + switch (val) { + case Calendar.ONE_CHAR: + Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_1CHAR.key).concat(); + break; + case Calendar.SHORT: + Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_SHORT.key).concat(); + break; + case Calendar.MEDIUM: + Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_MEDIUM.key).concat(); + break; + case Calendar.LONG: + Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_LONG.key).concat(); + break; + } + + var START_WEEKDAY = cfg.getProperty(DEF_CFG.START_WEEKDAY.key); + + if (START_WEEKDAY > 0) { + for (var w=0; w < START_WEEKDAY; ++w) { + Locale.LOCALE_WEEKDAYS.push(Locale.LOCALE_WEEKDAYS.shift()); + } + } + break; + } + }, - html[html.length] = ''; + /** + * The default handler for the "navigator" property + * @method configNavigator + */ + configNavigator : function(type, args, obj) { + var val = args[0]; + if (YAHOO.widget.CalendarNavigator && (val === true || Lang.isObject(val))) { + if (!this.oNavigator) { + this.oNavigator = new YAHOO.widget.CalendarNavigator(this); + // Cleanup DOM Refs/Events before innerHTML is removed. + this.beforeRenderEvent.subscribe(function () { + if (!this.pages) { + this.oNavigator.erase(); + } + }, this, true); + } + } else { + if (this.oNavigator) { + this.oNavigator.destroy(); + this.oNavigator = null; + } + } + }, - if (this.cfg.getProperty(defCfg.SHOW_WEEK_HEADER.key)) { - html[html.length] = ' '; - } + /** + * Defines the style constants for the Calendar + * @method initStyles + */ + initStyles : function() { - for(var i=0;i'; - } + var defStyle = Calendar._STYLES; - if (this.cfg.getProperty(defCfg.SHOW_WEEK_FOOTER.key)) { - html[html.length] = ' '; - } + this.Style = { + /** + * @property Style.CSS_ROW_HEADER + */ + CSS_ROW_HEADER: defStyle.CSS_ROW_HEADER, + /** + * @property Style.CSS_ROW_FOOTER + */ + CSS_ROW_FOOTER: defStyle.CSS_ROW_FOOTER, + /** + * @property Style.CSS_CELL + */ + CSS_CELL : defStyle.CSS_CELL, + /** + * @property Style.CSS_CELL_SELECTOR + */ + CSS_CELL_SELECTOR : defStyle.CSS_CELL_SELECTOR, + /** + * @property Style.CSS_CELL_SELECTED + */ + CSS_CELL_SELECTED : defStyle.CSS_CELL_SELECTED, + /** + * @property Style.CSS_CELL_SELECTABLE + */ + CSS_CELL_SELECTABLE : defStyle.CSS_CELL_SELECTABLE, + /** + * @property Style.CSS_CELL_RESTRICTED + */ + CSS_CELL_RESTRICTED : defStyle.CSS_CELL_RESTRICTED, + /** + * @property Style.CSS_CELL_TODAY + */ + CSS_CELL_TODAY : defStyle.CSS_CELL_TODAY, + /** + * @property Style.CSS_CELL_OOM + */ + CSS_CELL_OOM : defStyle.CSS_CELL_OOM, + /** + * @property Style.CSS_CELL_OOB + */ + CSS_CELL_OOB : defStyle.CSS_CELL_OOB, + /** + * @property Style.CSS_HEADER + */ + CSS_HEADER : defStyle.CSS_HEADER, + /** + * @property Style.CSS_HEADER_TEXT + */ + CSS_HEADER_TEXT : defStyle.CSS_HEADER_TEXT, + /** + * @property Style.CSS_BODY + */ + CSS_BODY : defStyle.CSS_BODY, + /** + * @property Style.CSS_WEEKDAY_CELL + */ + CSS_WEEKDAY_CELL : defStyle.CSS_WEEKDAY_CELL, + /** + * @property Style.CSS_WEEKDAY_ROW + */ + CSS_WEEKDAY_ROW : defStyle.CSS_WEEKDAY_ROW, + /** + * @property Style.CSS_FOOTER + */ + CSS_FOOTER : defStyle.CSS_FOOTER, + /** + * @property Style.CSS_CALENDAR + */ + CSS_CALENDAR : defStyle.CSS_CALENDAR, + /** + * @property Style.CSS_SINGLE + */ + CSS_SINGLE : defStyle.CSS_SINGLE, + /** + * @property Style.CSS_CONTAINER + */ + CSS_CONTAINER : defStyle.CSS_CONTAINER, + /** + * @property Style.CSS_NAV_LEFT + */ + CSS_NAV_LEFT : defStyle.CSS_NAV_LEFT, + /** + * @property Style.CSS_NAV_RIGHT + */ + CSS_NAV_RIGHT : defStyle.CSS_NAV_RIGHT, + /** + * @property Style.CSS_NAV + */ + CSS_NAV : defStyle.CSS_NAV, + /** + * @property Style.CSS_CLOSE + */ + CSS_CLOSE : defStyle.CSS_CLOSE, + /** + * @property Style.CSS_CELL_TOP + */ + CSS_CELL_TOP : defStyle.CSS_CELL_TOP, + /** + * @property Style.CSS_CELL_LEFT + */ + CSS_CELL_LEFT : defStyle.CSS_CELL_LEFT, + /** + * @property Style.CSS_CELL_RIGHT + */ + CSS_CELL_RIGHT : defStyle.CSS_CELL_RIGHT, + /** + * @property Style.CSS_CELL_BOTTOM + */ + CSS_CELL_BOTTOM : defStyle.CSS_CELL_BOTTOM, + /** + * @property Style.CSS_CELL_HOVER + */ + CSS_CELL_HOVER : defStyle.CSS_CELL_HOVER, + /** + * @property Style.CSS_CELL_HIGHLIGHT1 + */ + CSS_CELL_HIGHLIGHT1 : defStyle.CSS_CELL_HIGHLIGHT1, + /** + * @property Style.CSS_CELL_HIGHLIGHT2 + */ + CSS_CELL_HIGHLIGHT2 : defStyle.CSS_CELL_HIGHLIGHT2, + /** + * @property Style.CSS_CELL_HIGHLIGHT3 + */ + CSS_CELL_HIGHLIGHT3 : defStyle.CSS_CELL_HIGHLIGHT3, + /** + * @property Style.CSS_CELL_HIGHLIGHT4 + */ + CSS_CELL_HIGHLIGHT4 : defStyle.CSS_CELL_HIGHLIGHT4 + }; + }, - html[html.length] = ''; + /** + * Builds the date label that will be displayed in the calendar header or + * footer, depending on configuration. + * @method buildMonthLabel + * @return {String} The formatted calendar month label + */ + buildMonthLabel : function() { + return this._buildMonthLabel(this.cfg.getProperty(DEF_CFG.PAGEDATE.key)); + }, - return html; -}; + /** + * Helper method, to format a Month Year string, given a JavaScript Date, based on the + * Calendar localization settings + * + * @method _buildMonthLabel + * @private + * @param {Date} date + * @return {String} Formated month, year string + */ + _buildMonthLabel : function(date) { + var monthLabel = this.Locale.LOCALE_MONTHS[date.getMonth()] + this.Locale.MY_LABEL_MONTH_SUFFIX, + yearLabel = date.getFullYear() + this.Locale.MY_LABEL_YEAR_SUFFIX; -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.renderBody = function(workingDate, html) { - var defCfg = YAHOO.widget.Calendar._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; - } + if (this.Locale.MY_LABEL_MONTH_POSITION == 2 || this.Locale.MY_LABEL_YEAR_POSITION == 1) { + return yearLabel + monthLabel; + } else { + return monthLabel + yearLabel; + } + }, - this.monthDays = YAHOO.widget.DateMath.findMonthEnd(workingDate).getDate(); - this.postMonthDays = YAHOO.widget.Calendar.DISPLAY_DAYS-this.preMonthDays-this.monthDays; + /** + * Builds the date digit that will be displayed in calendar cells + * @method buildDayLabel + * @param {Date} workingDate The current working date + * @return {String} The formatted day label + */ + buildDayLabel : function(workingDate) { + return workingDate.getDate(); + }, - workingDate = YAHOO.widget.DateMath.subtract(workingDate, YAHOO.widget.DateMath.DAY, this.preMonthDays); - - var weekNum,weekClass; - var weekPrefix = "w"; - var cellPrefix = "_cell"; - var workingDayPrefix = "wd"; - var dayPrefix = "d"; + /** + * Creates the title bar element and adds it to Calendar container DIV + * + * @method createTitleBar + * @param {String} strTitle The title to display in the title bar + * @return The title bar element + */ + createTitleBar : function(strTitle) { + var tDiv = Dom.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE, "div", this.oDomContainer)[0] || document.createElement("div"); + tDiv.className = YAHOO.widget.CalendarGroup.CSS_2UPTITLE; + tDiv.innerHTML = strTitle; + this.oDomContainer.insertBefore(tDiv, this.oDomContainer.firstChild); - var cellRenderers; - var renderer; + Dom.addClass(this.oDomContainer, "withtitle"); - var todayYear = this.today.getFullYear(); - var todayMonth = this.today.getMonth(); - var todayDate = this.today.getDate(); + return tDiv; + }, - var useDate = this.cfg.getProperty(defCfg.PAGEDATE.key); - var hideBlankWeeks = this.cfg.getProperty(defCfg.HIDE_BLANK_WEEKS.key); - var showWeekFooter = this.cfg.getProperty(defCfg.SHOW_WEEK_FOOTER.key); - var showWeekHeader = this.cfg.getProperty(defCfg.SHOW_WEEK_HEADER.key); - var mindate = this.cfg.getProperty(defCfg.MINDATE.key); - var maxdate = this.cfg.getProperty(defCfg.MAXDATE.key); + /** + * Removes the title bar element from the DOM + * + * @method removeTitleBar + */ + removeTitleBar : function() { + var tDiv = Dom.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE, "div", this.oDomContainer)[0] || null; + if (tDiv) { + Event.purgeElement(tDiv); + this.oDomContainer.removeChild(tDiv); + } + Dom.removeClass(this.oDomContainer, "withtitle"); + }, + + /** + * Creates the close button HTML element and adds it to Calendar container DIV + * + * @method createCloseButton + * @return The close HTML element created + */ + createCloseButton : function() { + var cssClose = YAHOO.widget.CalendarGroup.CSS_2UPCLOSE, + DEPR_CLOSE_PATH = "us/my/bn/x_d.gif", + lnk = Dom.getElementsByClassName("link-close", "a", this.oDomContainer)[0], + strings = this.cfg.getProperty(DEF_CFG.STRINGS.key), + closeStr = (strings && strings.close) ? strings.close : ""; - if (mindate) { - mindate = YAHOO.widget.DateMath.clearTime(mindate); - } - if (maxdate) { - maxdate = YAHOO.widget.DateMath.clearTime(maxdate); - } + if (!lnk) { + lnk = document.createElement("a"); + Event.addListener(lnk, "click", function(e, cal) { + cal.hide(); + Event.preventDefault(e); + }, this); + } + + lnk.href = "#"; + lnk.className = "link-close"; + + if (Calendar.IMG_ROOT !== null) { + var img = Dom.getElementsByClassName(cssClose, "img", lnk)[0] || document.createElement("img"); + img.src = Calendar.IMG_ROOT + DEPR_CLOSE_PATH; + img.className = cssClose; + lnk.appendChild(img); + } else { + lnk.innerHTML = '' + closeStr + ''; + } + this.oDomContainer.appendChild(lnk); + + return lnk; + }, - html[html.length] = ''; - - var i = 0; + /** + * Removes the close button HTML element from the DOM + * + * @method removeCloseButton + */ + removeCloseButton : function() { + var btn = Dom.getElementsByClassName("link-close", "a", this.oDomContainer)[0] || null; + if (btn) { + Event.purgeElement(btn); + this.oDomContainer.removeChild(btn); + } + }, - var tempDiv = document.createElement("div"); - var cell = document.createElement("td"); - tempDiv.appendChild(cell); + /** + * Renders the calendar header. + * @method renderHeader + * @param {Array} html The current working HTML array + * @return {Array} The current working HTML array + */ + renderHeader : function(html) { - var jan1 = new Date(useDate.getFullYear(),0,1); - var cal = this.parent || this; + var colSpan = 7, + DEPR_NAV_LEFT = "us/tr/callt.gif", + DEPR_NAV_RIGHT = "us/tr/calrt.gif", + cfg = this.cfg, + pageDate = cfg.getProperty(DEF_CFG.PAGEDATE.key), + strings= cfg.getProperty(DEF_CFG.STRINGS.key), + prevStr = (strings && strings.previousMonth) ? strings.previousMonth : "", + nextStr = (strings && strings.nextMonth) ? strings.nextMonth : "", + monthLabel; - for (var r=0;r<6;r++) { + if (cfg.getProperty(DEF_CFG.SHOW_WEEK_HEADER.key)) { + colSpan += 1; + } + + if (cfg.getProperty(DEF_CFG.SHOW_WEEK_FOOTER.key)) { + colSpan += 1; + } - weekNum = YAHOO.widget.DateMath.getWeekNumber(workingDate, useDate.getFullYear(), startDay); - weekClass = weekPrefix + weekNum; + html[html.length] = ""; + html[html.length] = ""; + html[html.length] = ''; + html[html.length] = '
'; - // Local OOM check for performance, since we already have pagedate - if (r !== 0 && hideBlankWeeks === true && workingDate.getMonth() != useDate.getMonth()) { - break; + var renderLeft, renderRight = false; + + if (this.parent) { + if (this.index === 0) { + renderLeft = true; + } + if (this.index == (this.parent.cfg.getProperty("pages") -1)) { + renderRight = true; + } } else { + renderLeft = true; + renderRight = true; + } - html[html.length] = ''; - - if (showWeekHeader) { html = this.renderRowHeader(weekNum, html); } - - for (var d=0;d<7;d++){ // Render actual days + if (renderLeft) { + monthLabel = this._buildMonthLabel(DateMath.subtract(pageDate, DateMath.MONTH, 1)); - cellRenderers = []; - renderer = null; + var leftArrow = cfg.getProperty(DEF_CFG.NAV_ARROW_LEFT.key); + // Check for deprecated customization - If someone set IMG_ROOT, but didn't set NAV_ARROW_LEFT, then set NAV_ARROW_LEFT to the old deprecated value + if (leftArrow === null && Calendar.IMG_ROOT !== null) { + leftArrow = Calendar.IMG_ROOT + DEPR_NAV_LEFT; + } + var leftStyle = (leftArrow === null) ? "" : ' style="background-image:url(' + leftArrow + ')"'; + html[html.length] = '' + prevStr + ' (' + monthLabel + ')' + ''; + } - this.clearElement(cell); - cell.className = this.Style.CSS_CELL; - cell.id = this.id + cellPrefix + i; + var lbl = this.buildMonthLabel(); + var cal = this.parent || this; + if (cal.cfg.getProperty("navigator")) { + lbl = "" + lbl + ""; + } + html[html.length] = lbl; - 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 { - YAHOO.util.Dom.addClass(cell, workingDayPrefix + workingDate.getDay()); - YAHOO.util.Dom.addClass(cell, dayPrefix + workingDate.getDate()); - - for (var s=0;s' + nextStr + ' (' + monthLabel + ')' + ''; + } - if (workingDate.getMonth()+1 == month && workingDate.getDate() == day && workingDate.getFullYear() == year) { - renderer = rArray[2]; - this.renderStack.splice(s,1); - } - break; - case YAHOO.widget.Calendar.MONTH_DAY: - month = rArray[1][0]; - day = rArray[1][1]; - - if (workingDate.getMonth()+1 == month && workingDate.getDate() == day) { - renderer = rArray[2]; - this.renderStack.splice(s,1); - } - break; - case YAHOO.widget.Calendar.RANGE: - var date1 = rArray[1][0]; - var date2 = rArray[1][1]; + html[html.length] = '
\n\n'; - var d1month = date1[1]; - var d1day = date1[2]; - var d1year = date1[0]; - - var d1 = new Date(d1year, d1month-1, d1day); + if (cfg.getProperty(DEF_CFG.SHOW_WEEKDAYS.key)) { + html = this.buildWeekdays(html); + } + + html[html.length] = ''; + + return html; + }, + + /** + * Renders the Calendar's weekday headers. + * @method buildWeekdays + * @param {Array} html The current working HTML array + * @return {Array} The current working HTML array + */ + buildWeekdays : function(html) { - var d2month = date2[1]; - var d2day = date2[2]; - var d2year = date2[0]; + html[html.length] = ''; - var d2 = new Date(d2year, d2month-1, d2day); + if (this.cfg.getProperty(DEF_CFG.SHOW_WEEK_HEADER.key)) { + html[html.length] = ' '; + } - if (workingDate.getTime() >= d1.getTime() && workingDate.getTime() <= d2.getTime()) { - renderer = rArray[2]; + for(var i=0;i < this.Locale.LOCALE_WEEKDAYS.length; ++i) { + html[html.length] = '' + this.Locale.LOCALE_WEEKDAYS[i] + ''; + } - if (workingDate.getTime()==d2.getTime()) { - this.renderStack.splice(s,1); - } - } - break; - case YAHOO.widget.Calendar.WEEKDAY: - - var weekday = rArray[1][0]; - if (workingDate.getDay()+1 == weekday) { - renderer = rArray[2]; - } - break; - case YAHOO.widget.Calendar.MONTH: - - month = rArray[1][0]; - if (workingDate.getMonth()+1 == month) { - renderer = rArray[2]; - } - break; - } - - if (renderer) { - cellRenderers[cellRenderers.length]=renderer; - } - } + if (this.cfg.getProperty(DEF_CFG.SHOW_WEEK_FOOTER.key)) { + html[html.length] = ' '; + } - } + html[html.length] = ''; - if (this._indexOfSelectedFieldArray(workingArray) > -1) { - cellRenderers[cellRenderers.length]=cal.renderCellStyleSelected; - } + 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) { - 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) == YAHOO.widget.Calendar.STOP_RENDER) { - break; - } - } + var startDay = this.cfg.getProperty(DEF_CFG.START_WEEKDAY.key); - workingDate.setTime(workingDate.getTime() + YAHOO.widget.DateMath.ONE_DAY_MS); + this.preMonthDays = workingDate.getDay(); + if (startDay > 0) { + this.preMonthDays -= startDay; + } + if (this.preMonthDays < 0) { + this.preMonthDays += 7; + } - if (i >= 0 && i <= 6) { - YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_TOP); - } - if ((i % 7) === 0) { - YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_LEFT); - } - if (((i+1) % 7) === 0) { - YAHOO.util.Dom.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)) { - YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_BOTTOM); - } + this.monthDays = DateMath.findMonthEnd(workingDate).getDate(); + this.postMonthDays = Calendar.DISPLAY_DAYS-this.preMonthDays-this.monthDays; - html[html.length] = tempDiv.innerHTML; - i++; - } - if (showWeekFooter) { html = this.renderRowFooter(weekNum, html); } + workingDate = DateMath.subtract(workingDate, DateMath.DAY, this.preMonthDays); + + var weekNum, + weekClass, + weekPrefix = "w", + cellPrefix = "_cell", + workingDayPrefix = "wd", + dayPrefix = "d", + cellRenderers, + renderer, + t = this.today, + cfg = this.cfg, + todayYear = t.getFullYear(), + todayMonth = t.getMonth(), + todayDate = t.getDate(), + useDate = cfg.getProperty(DEF_CFG.PAGEDATE.key), + hideBlankWeeks = cfg.getProperty(DEF_CFG.HIDE_BLANK_WEEKS.key), + showWeekFooter = cfg.getProperty(DEF_CFG.SHOW_WEEK_FOOTER.key), + showWeekHeader = cfg.getProperty(DEF_CFG.SHOW_WEEK_HEADER.key), + mindate = cfg.getProperty(DEF_CFG.MINDATE.key), + maxdate = cfg.getProperty(DEF_CFG.MAXDATE.key); - html[html.length] = ''; + if (mindate) { + mindate = DateMath.clearTime(mindate); } - } + if (maxdate) { + maxdate = DateMath.clearTime(maxdate); + } - html[html.length] = ''; + html[html.length] = ''; - return html; -}; + var i = 0, + tempDiv = document.createElement("div"), + cell = document.createElement("td"); -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.renderFooter = function(html) { return html; }; + tempDiv.appendChild(cell); -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.render = function() { - this.beforeRenderEvent.fire(); + var cal = this.parent || this; - var defCfg = YAHOO.widget.Calendar._DEFAULT_CONFIG; + for (var r=0;r<6;r++) { + weekNum = DateMath.getWeekNumber(workingDate, startDay); + weekClass = weekPrefix + weekNum; - // Find starting day of the current month - var workingDate = YAHOO.widget.DateMath.findMonthStart(this.cfg.getProperty(defCfg.PAGEDATE.key)); + // Local OOM check for performance, since we already have pagedate + if (r !== 0 && hideBlankWeeks === true && workingDate.getMonth() != useDate.getMonth()) { + break; + } else { + html[html.length] = ''; - this.resetRenderers(); - this.cellDates.length = 0; + if (showWeekHeader) { html = this.renderRowHeader(weekNum, html); } - YAHOO.util.Event.purgeElement(this.oDomContainer, true); + for (var d=0; d < 7; d++){ // Render actual days - var html = []; + cellRenderers = []; - html[html.length] = ''; - html = this.renderHeader(html); - html = this.renderBody(workingDate, html); - html = this.renderFooter(html); - html[html.length] = '
'; + this.clearElement(cell); + cell.className = this.Style.CSS_CELL; + cell.id = this.id + cellPrefix + i; - this.oDomContainer.innerHTML = html.join("\n"); + if (workingDate.getDate() == todayDate && + workingDate.getMonth() == todayMonth && + workingDate.getFullYear() == todayYear) { + cellRenderers[cellRenderers.length]=cal.renderCellStyleToday; + } - this.applyListeners(); - this.cells = this.oDomContainer.getElementsByTagName("td"); + var workingArray = [workingDate.getFullYear(),workingDate.getMonth()+1,workingDate.getDate()]; + this.cellDates[this.cellDates.length] = workingArray; // Add this date to cellDates - this.cfg.refireEvent(defCfg.TITLE.key); - this.cfg.refireEvent(defCfg.CLOSE.key); - this.cfg.refireEvent(defCfg.IFRAME.key); + // Local OOM check for performance, since we already have pagedate + if (workingDate.getMonth() != useDate.getMonth()) { + cellRenderers[cellRenderers.length]=cal.renderCellNotThisMonth; + } else { + Dom.addClass(cell, workingDayPrefix + workingDate.getDay()); + Dom.addClass(cell, dayPrefix + workingDate.getDate()); - this.renderEvent.fire(); -}; + for (var s=0;s 0) { - this.linkLeft = linkLeft[0]; - YAHOO.util.Event.addListener(this.linkLeft, mousedown, cal.previousMonth, cal, true); - } + switch (type) { + case Calendar.DATE: + month = rArray[1][1]; + day = rArray[1][2]; + year = rArray[1][0]; - if (linkRight && linkRight.length > 0) { - this.linkRight = linkRight[0]; - YAHOO.util.Event.addListener(this.linkRight, mousedown, cal.nextMonth, cal, true); - } + if (workingDate.getMonth()+1 == month && workingDate.getDate() == day && workingDate.getFullYear() == year) { + renderer = rArray[2]; + this.renderStack.splice(s,1); + } + break; + case Calendar.MONTH_DAY: + month = rArray[1][0]; + day = rArray[1][1]; - 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 (workingDate.getMonth()+1 == month && workingDate.getDate() == day) { + renderer = rArray[2]; + this.renderStack.splice(s,1); + } + break; + case Calendar.RANGE: + var date1 = rArray[1][0], + date2 = rArray[1][1], + d1month = date1[1], + d1day = date1[2], + d1year = date1[0], + d1 = DateMath.getDate(d1year, d1month-1, d1day), + d2month = date2[1], + d2day = date2[2], + d2year = date2[0], + d2 = DateMath.getDate(d2year, d2month-1, d2day); - if (! (items instanceof Array)) { - items = [items]; - } + if (workingDate.getTime() >= d1.getTime() && workingDate.getTime() <= d2.getTime()) { + renderer = rArray[2]; - for (var i=0;i -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) == Calendar.STOP_RENDER) { + break; + } + } + + workingDate.setTime(workingDate.getTime() + DateMath.ONE_DAY_MS); + // Just in case we crossed DST/Summertime boundaries + workingDate = DateMath.clearTime(workingDate); + + if (i >= 0 && i <= 6) { + Dom.addClass(cell, this.Style.CSS_CELL_TOP); + } + if ((i % 7) === 0) { + Dom.addClass(cell, this.Style.CSS_CELL_LEFT); + } + if (((i+1) % 7) === 0) { + Dom.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)) { + Dom.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(); - YAHOO.util.Event.addListener(this.oDomContainer, "click", this.doSelectCell, this); - YAHOO.util.Event.addListener(this.oDomContainer, "mouseover", this.doCellMouseOver, this); - YAHOO.util.Event.addListener(this.oDomContainer, "mouseout", this.doCellMouseOut, this); -}; + // Find starting day of the current month + var workingDate = DateMath.findMonthStart(this.cfg.getProperty(DEF_CFG.PAGEDATE.key)); -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.getDateByCellId = function(id) { - var date = this.getDateFieldsByCellId(id); - return new Date(date[0],date[1]-1,date[2]); -}; + this.resetRenderers(); + this.cellDates.length = 0; -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.getDateFieldsByCellId = function(id) { - id = id.toLowerCase().split("_cell")[1]; - id = parseInt(id, 10); - return this.cellDates[id]; -}; + Event.purgeElement(this.oDomContainer, true); -// BEGIN BUILT-IN TABLE CELL RENDERERS + var html = []; -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.renderOutOfBoundsDate = function(workingDate, cell) { - YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_OOB); - cell.innerHTML = workingDate.getDate(); - return YAHOO.widget.Calendar.STOP_RENDER; -}; + html[html.length] = ''; + html = this.renderHeader(html); + html = this.renderBody(workingDate, html); + html = this.renderFooter(html); + html[html.length] = '
'; -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.renderRowHeader = function(weekNum, html) { - html[html.length] = '' + weekNum + ''; - return html; -}; + this.oDomContainer.innerHTML = html.join("\n"); -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.renderRowFooter = function(weekNum, html) { - html[html.length] = '' + weekNum + ''; - return html; -}; + this.applyListeners(); + this.cells = this.oDomContainer.getElementsByTagName("td"); + + this.cfg.refireEvent(DEF_CFG.TITLE.key); + this.cfg.refireEvent(DEF_CFG.CLOSE.key); + this.cfg.refireEvent(DEF_CFG.IFRAME.key); -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.renderCellDefault = function(workingDate, cell) { - cell.innerHTML = '' + this.buildDayLabel(workingDate) + ""; -}; + this.renderEvent.fire(); + }, -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.styleCellDefault = function(workingDate, cell) { - YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_SELECTABLE); -}; + /** + * Applies the Calendar's DOM listeners to applicable elements. + * @method applyListeners + */ + applyListeners : function() { + var root = this.oDomContainer, + cal = this.parent || this, + anchor = "a", + click = "click"; + var linkLeft = Dom.getElementsByClassName(this.Style.CSS_NAV_LEFT, anchor, root), + linkRight = Dom.getElementsByClassName(this.Style.CSS_NAV_RIGHT, anchor, root); -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.renderCellStyleHighlight1 = function(workingDate, cell) { - YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT1); -}; + if (linkLeft && linkLeft.length > 0) { + this.linkLeft = linkLeft[0]; + Event.addListener(this.linkLeft, click, this.doPreviousMonthNav, cal, true); + } -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.renderCellStyleHighlight2 = function(workingDate, cell) { - YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT2); -}; + if (linkRight && linkRight.length > 0) { + this.linkRight = linkRight[0]; + Event.addListener(this.linkRight, click, this.doNextMonthNav, cal, true); + } -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.renderCellStyleHighlight3 = function(workingDate, cell) { - YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT3); -}; + if (cal.cfg.getProperty("navigator") !== null) { + this.applyNavListeners(); + } -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.renderCellStyleHighlight4 = function(workingDate, cell) { - YAHOO.util.Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT4); -}; + if (this.domEventMap) { + var el,elements; + for (var cls in this.domEventMap) { + if (Lang.hasOwnProperty(this.domEventMap, cls)) { + var items = this.domEventMap[cls]; + + if (! (items instanceof Array)) { + items = [items]; + } + + for (var i=0;i 0) { -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.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; -}; + Event.addListener(navBtns, "click", function (e, obj) { + var target = Event.getTarget(e); + // this == navBtn + if (this === target || Dom.isAncestor(this, target)) { + Event.preventDefault(e); + } + var navigator = calParent.oNavigator; + if (navigator) { + var pgdate = cal.cfg.getProperty("pagedate"); + navigator.setYear(pgdate.getFullYear()); + navigator.setMonth(pgdate.getMonth()); + navigator.show(); + } + }); + } + }, -// END BUILT-IN TABLE CELL RENDERERS + /** + * 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 (date) ? DateMath.getDate(date[0],date[1]-1,date[2]) : null; + }, + + /** + * 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 = this.getIndexFromId(id); + return (id > -1) ? this.cellDates[id] : null; + }, -// BEGIN MONTH NAVIGATION METHODS + /** + * 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; -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.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(); -}; + 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; + }, -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.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(); -}; + /** + * Given the id used to mark each Calendar cell, this method + * extracts the index number from the id. + * + * @param {String} strId The cell id + * @return {Number} The index of the cell, or -1 if id does not contain an index number + */ + getIndexFromId : function(strId) { + var idx = -1, + li = strId.lastIndexOf("_cell"); -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.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(); -}; + if (li > -1) { + idx = parseInt(strId.substring(li + 5), 10); + } -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.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(); -}; + 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) { + Dom.addClass(cell, this.Style.CSS_CELL_OOB); + cell.innerHTML = workingDate.getDate(); + return 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + Dom.addClass(cell, this.Style.CSS_CELL_OOM); + cell.innerHTML=workingDate.getDate(); + return 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) { + Dom.addClass(cell, this.Style.CSS_CELL); + Dom.addClass(cell, this.Style.CSS_CELL_RESTRICTED); + cell.innerHTML=workingDate.getDate(); + return 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 = DEF_CFG.PAGEDATE.key; + this.cfg.setProperty(cfgPageDate, DateMath.add(this.cfg.getProperty(cfgPageDate), 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 = DEF_CFG.PAGEDATE.key; + this.cfg.setProperty(cfgPageDate, DateMath.subtract(this.cfg.getProperty(cfgPageDate), DateMath.MONTH, count)); + this.resetRenderers(); + this.changePageEvent.fire(); + }, -/** -* Navigates to the next month page in the calendar widget. -* @method nextMonth -*/ -YAHOO.widget.Calendar.prototype.nextMonth = function() { - this.addMonths(1); -}; + /** + * 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 = DEF_CFG.PAGEDATE.key; + this.cfg.setProperty(cfgPageDate, DateMath.add(this.cfg.getProperty(cfgPageDate), 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 = DEF_CFG.PAGEDATE.key; + this.cfg.setProperty(cfgPageDate, DateMath.subtract(this.cfg.getProperty(cfgPageDate), 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() { + this.cfg.resetProperty(DEF_CFG.SELECTED.key); + this.cfg.resetProperty(DEF_CFG.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() { + this.cfg.setProperty(DEF_CFG.SELECTED.key, []); + this.cfg.setProperty(DEF_CFG.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) { -/** -* Navigates to the previous month page in the calendar widget. -* @method previousMonth -*/ -YAHOO.widget.Calendar.prototype.previousMonth = function() { - this.subtractMonths(1); -}; + var aToBeSelected = this._toFieldArray(date), + validDates = [], + selected = [], + cfgSelected = DEF_CFG.SELECTED.key; -/** -* Navigates to the next year in the currently selected month in the calendar widget. -* @method nextYear -*/ -YAHOO.widget.Calendar.prototype.nextYear = function() { - this.addYears(1); -}; + + for (var a=0; a < aToBeSelected.length; ++a) { + var toSelect = aToBeSelected[a]; -/** -* Navigates to the previous year in the currently selected month in the calendar widget. -* @method previousYear -*/ -YAHOO.widget.Calendar.prototype.previousYear = function() { - this.subtractYears(1); -}; + if (!this.isDateOOB(this._toDate(toSelect))) { -// END MONTH NAVIGATION METHODS + if (validDates.length === 0) { + this.beforeSelectEvent.fire(); + selected = this.cfg.getProperty(cfgSelected); + } + validDates.push(toSelect); -// BEGIN SELECTION METHODS + if (this._indexOfSelectedFieldArray(toSelect) == -1) { + selected[selected.length] = toSelect; + } + } + } -/** -* Resets the calendar widget to the originally selected month and year, and -* sets the calendar to the initial selection(s). -* @method reset -*/ -YAHOO.widget.Calendar.prototype.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 -*/ -YAHOO.widget.Calendar.prototype.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(); -}; + if (validDates.length > 0) { + if (this.parent) { + this.parent.cfg.setProperty(cfgSelected, selected); + } else { + this.cfg.setProperty(cfgSelected, selected); + } + this.selectEvent.fire(validDates); + } -/** -* 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. -*/ -YAHOO.widget.Calendar.prototype.select = function(date) { + 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 aToBeSelected = this._toFieldArray(date); + var cell = this.cells[cellIndex], + cellDate = this.cellDates[cellIndex], + dCellDate = this._toDate(cellDate), + selectable = Dom.hasClass(cell, this.Style.CSS_CELL_SELECTABLE); - // 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); + if (selectable) { + + this.beforeSelectEvent.fire(); + + var cfgSelected = DEF_CFG.SELECTED.key; + var selected = this.cfg.getProperty(cfgSelected); + + var selectDate = cellDate.concat(); + + if (this._indexOfSelectedFieldArray(selectDate) == -1) { + selected[selected.length] = selectDate; } - - validDates.push(toSelect); - - if (this._indexOfSelectedFieldArray(toSelect) == -1) { - selected[selected.length] = toSelect; + 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) { - if (validDates.length > 0) { - if (this.parent) { - this.parent.cfg.setProperty(cfgSelected, selected); - } else { - this.cfg.setProperty(cfgSelected, selected); - } - this.selectEvent.fire(validDates); - } + var aToBeDeselected = this._toFieldArray(date), + validDates = [], + selected = [], + cfgSelected = DEF_CFG.SELECTED.key; - 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. -*/ -YAHOO.widget.Calendar.prototype.selectCell = function(cellIndex) { - - var cell = this.cells[cellIndex]; - var cellDate = this.cellDates[cellIndex]; - var dCellDate = this._toDate(cellDate); + for (var a=0; a < aToBeDeselected.length; ++a) { + var toDeselect = aToBeDeselected[a]; - var selectable = YAHOO.util.Dom.hasClass(cell, this.Style.CSS_CELL_SELECTABLE); + 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], + cellDate = this.cellDates[cellIndex], + cellDateIndex = this._indexOfSelectedFieldArray(cellDate); - if (selectable) { + var selectable = Dom.hasClass(cell, this.Style.CSS_CELL_SELECTABLE); - this.beforeSelectEvent.fire(); + if (selectable) { - var cfgSelected = YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key; - var selected = this.cfg.getProperty(cfgSelected); + this.beforeDeselectEvent.fire(); - var selectDate = cellDate.concat(); + var selected = this.cfg.getProperty(DEF_CFG.SELECTED.key), + dCellDate = this._toDate(cellDate), + selectDate = cellDate.concat(); - if (this._indexOfSelectedFieldArray(selectDate) == -1) { - selected[selected.length] = selectDate; + if (cellDateIndex > -1) { + if (this.cfg.getProperty(DEF_CFG.PAGEDATE.key).getMonth() == dCellDate.getMonth() && + this.cfg.getProperty(DEF_CFG.PAGEDATE.key).getFullYear() == dCellDate.getFullYear()) { + Dom.removeClass(cell, this.Style.CSS_CELL_SELECTED); + } + selected.splice(cellDateIndex, 1); + } + + if (this.parent) { + this.parent.cfg.setProperty(DEF_CFG.SELECTED.key, selected); + } else { + this.cfg.setProperty(DEF_CFG.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 = DEF_CFG.SELECTED.key, + selected = this.cfg.getProperty(cfgSelected), + count = selected.length, + sel = selected.concat(); + if (this.parent) { - this.parent.cfg.setProperty(cfgSelected, selected); + this.parent.cfg.setProperty(cfgSelected, []); } else { - this.cfg.setProperty(cfgSelected, selected); + this.cfg.setProperty(cfgSelected, []); } - this.renderCellStyleSelected(dCellDate,cell); - this.selectEvent.fire([selectDate]); + + 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 (Lang.isString(date)) { + returnDate = this._parseDates(date); + } else if (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; - this.doCellMouseOut.call(cell, null, this); - } + if (date) { + if (date instanceof Date) { + parsedDate = DateMath.findMonthStart(date); + } else { + var month, year, aMonthYear; + aMonthYear = date.split(this.cfg.getProperty(DEF_CFG.DATE_FIELD_DELIMITER.key)); + month = parseInt(aMonthYear[this.cfg.getProperty(DEF_CFG.MY_MONTH_POSITION.key)-1], 10)-1; + year = parseInt(aMonthYear[this.cfg.getProperty(DEF_CFG.MY_YEAR_POSITION.key)-1], 10); - return this.getSelectedDates(); -}; + parsedDate = DateMath.getDate(year, month, 1); + } + } else { + parsedDate = 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(DEF_CFG.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(); + }, -/** -* 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. -*/ -YAHOO.widget.Calendar.prototype.deselect = function(date) { + /** + * Event executed when the calendar widget is rendered. + * @deprecated Event handlers for this event should be susbcribed to renderEvent. + */ + onRender : function() { }, - var aToBeDeselected = this._toFieldArray(date); + /** + * 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(); }, - var validDates = []; - var selected = []; - var cfgSelected = YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key; + /** + * 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), + rArray; + + if (aDate.length == 2) { + rArray = [aDate[this.Locale.MD_MONTH_POSITION-1],aDate[this.Locale.MD_DAY_POSITION-1]]; + rArray.type = 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 = Calendar.DATE; + } + + for (var i=0;i 0) { - if (this.parent) { - this.parent.cfg.setProperty(cfgSelected, selected); - } else { - this.cfg.setProperty(cfgSelected, selected); + /** + * Adds a weekday to the render stack. The function reference passed to this method will be executed + * when a date cell matches the weekday passed to this method. + * @method addWeekdayRenderer + * @param {Number} weekday The weekday (Sunday = 1, Monday = 2 ... Saturday = 7) to associate with this renderer + * @param {Function} fnRender The function executed to render cells that match the render rules for this renderer. + */ + addWeekdayRenderer : function(weekday, fnRender) { + this._addRenderer(Calendar.WEEKDAY,[weekday],fnRender); + }, + + // END RENDERER METHODS + + // BEGIN CSS METHODS + + /** + * Removes all styles from all body cells in the current calendar table. + * @method clearAllBodyCellStyles + * @param {style} style The CSS class name to remove from all calendar body cells + */ + clearAllBodyCellStyles : function(style) { + for (var c=0;c -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); + /// END GETTER/SETTER METHODS /// + + /** + * Hides the Calendar's outer container from view. + * @method hide + */ + hide : function() { + if (this.beforeHideEvent.fire()) { + this.oDomContainer.style.display = "none"; + this.hideEvent.fire(); } + }, - if (this.parent) { - this.parent.cfg.setProperty(defCfg.SELECTED.key, selected); - } else { - this.cfg.setProperty(defCfg.SELECTED.key, selected); + /** + * Shows the Calendar's outer container. + * @method show + */ + show : function() { + if (this.beforeShowEvent.fire()) { + this.oDomContainer.style.display = "block"; + this.showEvent.fire(); } + }, - this.deselectEvent.fire(selectDate); - } + /** + * Returns a string representing the current browser. + * @deprecated As of 2.3.0, environment information is available in YAHOO.env.ua + * @see YAHOO.env.ua + * @property browser + * @type String + */ + browser : (function() { + var ua = navigator.userAgent.toLowerCase(); + if (ua.indexOf('opera')!=-1) { // Opera (check first in case of spoof) + return 'opera'; + } else if (ua.indexOf('msie 7')!=-1) { // IE7 + return 'ie7'; + } else if (ua.indexOf('msie') !=-1) { // IE + return 'ie'; + } else if (ua.indexOf('safari')!=-1) { // Safari (check before Gecko because it includes "like Gecko") + return 'safari'; + } else if (ua.indexOf('gecko') != -1) { // Gecko + return 'gecko'; + } else { + return false; + } + })(), + /** + * Returns a string representation of the object. + * @method toString + * @return {String} A string representation of the Calendar object. + */ + toString : function() { + return "Calendar " + this.id; + }, - return this.getSelectedDates(); -}; + /** + * Destroys the Calendar instance. The method will remove references + * to HTML elements, remove any event listeners added by the Calendar, + * and destroy the Config and CalendarNavigator instances it has created. + * + * @method destroy + */ + destroy : function() { -/** -* 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. -*/ -YAHOO.widget.Calendar.prototype.deselectAll = function() { - this.beforeDeselectEvent.fire(); - - var cfgSelected = YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key; + if (this.beforeDestroyEvent.fire()) { + var cal = this; - var selected = this.cfg.getProperty(cfgSelected); - var count = selected.length; - var sel = selected.concat(); + // Child objects + if (cal.navigator) { + cal.navigator.destroy(); + } - if (this.parent) { - this.parent.cfg.setProperty(cfgSelected, []); - } else { - this.cfg.setProperty(cfgSelected, []); - } - - if (count > 0) { - this.deselectEvent.fire(sel); - } + if (cal.cfg) { + cal.cfg.destroy(); + } - return this.getSelectedDates(); -}; + // DOM event listeners + Event.purgeElement(cal.oDomContainer, true); -// END SELECTION METHODS + // Generated markup/DOM - Not removing the container DIV since we didn't create it. + Dom.removeClass(cal.oDomContainer, "withtitle"); + Dom.removeClass(cal.oDomContainer, cal.Style.CSS_CONTAINER); + Dom.removeClass(cal.oDomContainer, cal.Style.CSS_SINGLE); + cal.oDomContainer.innerHTML = ""; -// BEGIN TYPE CONVERSION METHODS + // JS-to-DOM references + cal.oDomContainer = null; + cal.cells = null; -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype._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 +*
+*
+* +* 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.Calendar.prototype._fieldArraysAreEqual = function(array1, array2) { - var match = false; - - if (array1[0]==array2[0]&&array1[1]==array2[1]&&array1[2]==array2[2]) { - match=true; +function CalendarGroup(id, containerId, config) { + if (arguments.length > 0) { + this.init.apply(this, arguments); } +} - return match; -}; - /** -* Gets the index of a date field array [yyyy,mm,dd] in the current list of selected dates. -* @method _indexOfSelectedFieldArray +* The set of default Config property keys and values for the CalendarGroup +* @property YAHOO.widget.CalendarGroup._DEFAULT_CONFIG +* @final +* @static * @private -* @param {Number[]} find The date field array to search for -* @return {Number} The index of the date field array within the collection of selected dates. -* -1 will be returned if the date is not found. +* @type Object */ -YAHOO.widget.Calendar.prototype._indexOfSelectedFieldArray = function(find) { - var selected = -1; - var seldates = this.cfg.getProperty(YAHOO.widget.Calendar._DEFAULT_CONFIG.SELECTED.key); +CalendarGroup._DEFAULT_CONFIG = Calendar._DEFAULT_CONFIG; +CalendarGroup._DEFAULT_CONFIG.PAGES = {key:"pages", value:2}; - for (var s=0;s maxDate.getTime())); -}; + this.oDomContainer = Dom.get(container); -/** - * 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 - */ -YAHOO.widget.Calendar.prototype._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 = new Date(year, month, 1); + if (!this.oDomContainer.id) { + this.oDomContainer.id = Dom.generateId(); } - } else { - parsedDate = new Date(this.today.getFullYear(), this.today.getMonth(), 1); - } - return parsedDate; -}; + if (!id) { + id = this.oDomContainer.id + "_t"; + } -// END UTILITY METHODS + /** + * The unique id associated with the CalendarGroup + * @property id + * @type String + */ + this.id = id; -// BEGIN EVENT HANDLERS + /** + * The unique id associated with the CalendarGroup container + * @property containerId + * @type String + */ + this.containerId = this.oDomContainer.id; -/** -* Event executed before a date is selected in the calendar widget. -* @deprecated Event handlers for this event should be susbcribed to beforeSelectEvent. -*/ -YAHOO.widget.Calendar.prototype.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(); - } - } -}; + this.initEvents(); + this.initStyles(); -/** -* 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. -*/ -YAHOO.widget.Calendar.prototype.onSelect = function(selected) { }; + /** + * The collection of Calendar pages contained within the CalendarGroup + * @property pages + * @type YAHOO.widget.Calendar[] + */ + this.pages = []; -/** -* Event executed before a date is deselected in the calendar widget. -* @deprecated Event handlers for this event should be susbcribed to beforeDeselectEvent. -*/ -YAHOO.widget.Calendar.prototype.onBeforeDeselect = function() { }; + Dom.addClass(this.oDomContainer, CalendarGroup.CSS_CONTAINER); + Dom.addClass(this.oDomContainer, CalendarGroup.CSS_MULTI_UP); -/** -* 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. -*/ -YAHOO.widget.Calendar.prototype.onDeselect = function(deselected) { }; + /** + * 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); -/** -* Event executed when the user navigates to a different calendar page. -* @deprecated Event handlers for this event should be susbcribed to changePageEvent. -*/ -YAHOO.widget.Calendar.prototype.onChangePage = function() { - this.render(); -}; + /** + * The local object which contains the CalendarGroup's options + * @property Options + * @type Object + */ + this.Options = {}; -/** -* Event executed when the calendar widget is rendered. -* @deprecated Event handlers for this event should be susbcribed to renderEvent. -*/ -YAHOO.widget.Calendar.prototype.onRender = function() { }; + /** + * The local object which contains the CalendarGroup's locale settings + * @property Locale + * @type Object + */ + this.Locale = {}; -/** -* Event executed when the calendar widget is reset to its original state. -* @deprecated Event handlers for this event should be susbcribed to resetEvemt. -*/ -YAHOO.widget.Calendar.prototype.onReset = function() { this.render(); }; + this.setupConfig(); -/** -* 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. -*/ -YAHOO.widget.Calendar.prototype.onClear = function() { this.render(); }; + if (config) { + this.cfg.applyConfig(config, true); + } -/** -* 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 -*/ -YAHOO.widget.Calendar.prototype.validate = function() { return true; }; + this.cfg.fireQueue(); -// END EVENT HANDLERS + // OPERA HACK FOR MISWRAPPED FLOATS + if (YAHOO.env.ua.opera){ + this.renderEvent.subscribe(this._fixWidth, this, true); + this.showEvent.subscribe(this._fixWidth, this, true); + } -// 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[]) -*/ -YAHOO.widget.Calendar.prototype._parseDate = function(sDate) { - var aDate = sDate.split(this.Locale.DATE_FIELD_DELIMITER); - var rArray; + setupConfig : function() { - 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; - } + var cfg = this.cfg; - for (var i=0;i + *
previousMonth
String : The string to use for the "Previous Month" navigation UI. Defaults to "Previous Month".
+ *
nextMonth
String : The string to use for the "Next Month" navigation UI. Defaults to "Next Month".
+ *
close
String : The string to use for the close button label. Defaults to "Close".
+ * + */ + cfg.addProperty(DEF_CFG.STRINGS.key, { + value:DEF_CFG.STRINGS.value, + handler:this.configStrings, + validator: function(val) { + return Lang.isObject(val); + }, + supercedes: DEF_CFG.STRINGS.supercedes + }); + }, -/** -* Resets the render stack of the current calendar to its original pre-render value. -*/ -YAHOO.widget.Calendar.prototype.resetRenderers = function() { - this.renderStack = this._renderStack.concat(); -}; + /** + * Initializes CalendarGroup's built-in CustomEvents + * @method initEvents + */ + initEvents : function() { -/** -* Clears the inner HTML, CSS class and style information from the specified cell. -* @method clearElement -* @param {HTMLTableCellElement} The cell to clear -*/ -YAHOO.widget.Calendar.prototype.clearElement = function(cell) { - cell.innerHTML = " "; - cell.className=""; -}; + var me = this, + strEvent = "Event", + CE = YAHOO.util.CustomEvent; -/** -* Adds a renderer to the render stack. The function reference passed to this method will be executed -* when a date cell matches the conditions specified in the date string for this renderer. -* @method addRenderer -* @param {String} sDates A date string to associate with the specified renderer. Valid formats -* include date (12/24/2005), month/day (12/24), and range (12/1/2004-1/1/2005) -* @param {Function} fnRender The function executed to render cells that match the render rules for this renderer. -*/ -YAHOO.widget.Calendar.prototype.addRenderer = function(sDates, fnRender) { - var aDates = this._parseDates(sDates); - for (var i=0;i