Merge remote branch 'kc/master' into new/enh/bug_5917
[koha.git] / koha-tt / intranet-tmpl / prog / en / lib / yui / calendar / calendar-debug.js
1 /*
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.8.0r4
6 */
7 (function () {
8
9     /**
10     * Config is a utility used within an Object to allow the implementer to
11     * maintain a list of local configuration properties and listen for changes 
12     * to those properties dynamically using CustomEvent. The initial values are 
13     * also maintained so that the configuration can be reset at any given point 
14     * to its initial state.
15     * @namespace YAHOO.util
16     * @class Config
17     * @constructor
18     * @param {Object} owner The owner Object to which this Config Object belongs
19     */
20     YAHOO.util.Config = function (owner) {
21
22         if (owner) {
23             this.init(owner);
24         }
25
26         if (!owner) {  YAHOO.log("No owner specified for Config object", "error", "Config"); }
27
28     };
29
30
31     var Lang = YAHOO.lang,
32         CustomEvent = YAHOO.util.CustomEvent,
33         Config = YAHOO.util.Config;
34
35
36     /**
37      * Constant representing the CustomEvent type for the config changed event.
38      * @property YAHOO.util.Config.CONFIG_CHANGED_EVENT
39      * @private
40      * @static
41      * @final
42      */
43     Config.CONFIG_CHANGED_EVENT = "configChanged";
44     
45     /**
46      * Constant representing the boolean type string
47      * @property YAHOO.util.Config.BOOLEAN_TYPE
48      * @private
49      * @static
50      * @final
51      */
52     Config.BOOLEAN_TYPE = "boolean";
53     
54     Config.prototype = {
55      
56         /**
57         * Object reference to the owner of this Config Object
58         * @property owner
59         * @type Object
60         */
61         owner: null,
62         
63         /**
64         * Boolean flag that specifies whether a queue is currently 
65         * being executed
66         * @property queueInProgress
67         * @type Boolean
68         */
69         queueInProgress: false,
70         
71         /**
72         * Maintains the local collection of configuration property objects and 
73         * their specified values
74         * @property config
75         * @private
76         * @type Object
77         */ 
78         config: null,
79         
80         /**
81         * Maintains the local collection of configuration property objects as 
82         * they were initially applied.
83         * This object is used when resetting a property.
84         * @property initialConfig
85         * @private
86         * @type Object
87         */ 
88         initialConfig: null,
89         
90         /**
91         * Maintains the local, normalized CustomEvent queue
92         * @property eventQueue
93         * @private
94         * @type Object
95         */ 
96         eventQueue: null,
97         
98         /**
99         * Custom Event, notifying subscribers when Config properties are set 
100         * (setProperty is called without the silent flag
101         * @event configChangedEvent
102         */
103         configChangedEvent: null,
104     
105         /**
106         * Initializes the configuration Object and all of its local members.
107         * @method init
108         * @param {Object} owner The owner Object to which this Config 
109         * Object belongs
110         */
111         init: function (owner) {
112     
113             this.owner = owner;
114     
115             this.configChangedEvent = 
116                 this.createEvent(Config.CONFIG_CHANGED_EVENT);
117     
118             this.configChangedEvent.signature = CustomEvent.LIST;
119             this.queueInProgress = false;
120             this.config = {};
121             this.initialConfig = {};
122             this.eventQueue = [];
123         
124         },
125         
126         /**
127         * Validates that the value passed in is a Boolean.
128         * @method checkBoolean
129         * @param {Object} val The value to validate
130         * @return {Boolean} true, if the value is valid
131         */ 
132         checkBoolean: function (val) {
133             return (typeof val == Config.BOOLEAN_TYPE);
134         },
135         
136         /**
137         * Validates that the value passed in is a number.
138         * @method checkNumber
139         * @param {Object} val The value to validate
140         * @return {Boolean} true, if the value is valid
141         */
142         checkNumber: function (val) {
143             return (!isNaN(val));
144         },
145         
146         /**
147         * Fires a configuration property event using the specified value. 
148         * @method fireEvent
149         * @private
150         * @param {String} key The configuration property's name
151         * @param {value} Object The value of the correct type for the property
152         */ 
153         fireEvent: function ( key, value ) {
154             YAHOO.log("Firing Config event: " + key + "=" + value, "info", "Config");
155             var property = this.config[key];
156         
157             if (property && property.event) {
158                 property.event.fire(value);
159             } 
160         },
161         
162         /**
163         * Adds a property to the Config Object's private config hash.
164         * @method addProperty
165         * @param {String} key The configuration property's name
166         * @param {Object} propertyObject The Object containing all of this 
167         * property's arguments
168         */
169         addProperty: function ( key, propertyObject ) {
170             key = key.toLowerCase();
171             YAHOO.log("Added property: " + key, "info", "Config");
172         
173             this.config[key] = propertyObject;
174         
175             propertyObject.event = this.createEvent(key, { scope: this.owner });
176             propertyObject.event.signature = CustomEvent.LIST;
177             
178             
179             propertyObject.key = key;
180         
181             if (propertyObject.handler) {
182                 propertyObject.event.subscribe(propertyObject.handler, 
183                     this.owner);
184             }
185         
186             this.setProperty(key, propertyObject.value, true);
187             
188             if (! propertyObject.suppressEvent) {
189                 this.queueProperty(key, propertyObject.value);
190             }
191             
192         },
193         
194         /**
195         * Returns a key-value configuration map of the values currently set in  
196         * the Config Object.
197         * @method getConfig
198         * @return {Object} The current config, represented in a key-value map
199         */
200         getConfig: function () {
201         
202             var cfg = {},
203                 currCfg = this.config,
204                 prop,
205                 property;
206                 
207             for (prop in currCfg) {
208                 if (Lang.hasOwnProperty(currCfg, prop)) {
209                     property = currCfg[prop];
210                     if (property && property.event) {
211                         cfg[prop] = property.value;
212                     }
213                 }
214             }
215
216             return cfg;
217         },
218         
219         /**
220         * Returns the value of specified property.
221         * @method getProperty
222         * @param {String} key The name of the property
223         * @return {Object}  The value of the specified property
224         */
225         getProperty: function (key) {
226             var property = this.config[key.toLowerCase()];
227             if (property && property.event) {
228                 return property.value;
229             } else {
230                 return undefined;
231             }
232         },
233         
234         /**
235         * Resets the specified property's value to its initial value.
236         * @method resetProperty
237         * @param {String} key The name of the property
238         * @return {Boolean} True is the property was reset, false if not
239         */
240         resetProperty: function (key) {
241     
242             key = key.toLowerCase();
243         
244             var property = this.config[key];
245     
246             if (property && property.event) {
247     
248                 if (this.initialConfig[key] && 
249                     !Lang.isUndefined(this.initialConfig[key])) {
250     
251                     this.setProperty(key, this.initialConfig[key]);
252
253                     return true;
254     
255                 }
256     
257             } else {
258     
259                 return false;
260             }
261     
262         },
263         
264         /**
265         * Sets the value of a property. If the silent property is passed as 
266         * true, the property's event will not be fired.
267         * @method setProperty
268         * @param {String} key The name of the property
269         * @param {String} value The value to set the property to
270         * @param {Boolean} silent Whether the value should be set silently, 
271         * without firing the property event.
272         * @return {Boolean} True, if the set was successful, false if it failed.
273         */
274         setProperty: function (key, value, silent) {
275         
276             var property;
277         
278             key = key.toLowerCase();
279             YAHOO.log("setProperty: " + key + "=" + value, "info", "Config");
280         
281             if (this.queueInProgress && ! silent) {
282                 // Currently running through a queue... 
283                 this.queueProperty(key,value);
284                 return true;
285     
286             } else {
287                 property = this.config[key];
288                 if (property && property.event) {
289                     if (property.validator && !property.validator(value)) {
290                         return false;
291                     } else {
292                         property.value = value;
293                         if (! silent) {
294                             this.fireEvent(key, value);
295                             this.configChangedEvent.fire([key, value]);
296                         }
297                         return true;
298                     }
299                 } else {
300                     return false;
301                 }
302             }
303         },
304         
305         /**
306         * Sets the value of a property and queues its event to execute. If the 
307         * event is already scheduled to execute, it is
308         * moved from its current position to the end of the queue.
309         * @method queueProperty
310         * @param {String} key The name of the property
311         * @param {String} value The value to set the property to
312         * @return {Boolean}  true, if the set was successful, false if 
313         * it failed.
314         */ 
315         queueProperty: function (key, value) {
316         
317             key = key.toLowerCase();
318             YAHOO.log("queueProperty: " + key + "=" + value, "info", "Config");
319         
320             var property = this.config[key],
321                 foundDuplicate = false,
322                 iLen,
323                 queueItem,
324                 queueItemKey,
325                 queueItemValue,
326                 sLen,
327                 supercedesCheck,
328                 qLen,
329                 queueItemCheck,
330                 queueItemCheckKey,
331                 queueItemCheckValue,
332                 i,
333                 s,
334                 q;
335                                 
336             if (property && property.event) {
337     
338                 if (!Lang.isUndefined(value) && property.validator && 
339                     !property.validator(value)) { // validator
340                     return false;
341                 } else {
342         
343                     if (!Lang.isUndefined(value)) {
344                         property.value = value;
345                     } else {
346                         value = property.value;
347                     }
348         
349                     foundDuplicate = false;
350                     iLen = this.eventQueue.length;
351         
352                     for (i = 0; i < iLen; i++) {
353                         queueItem = this.eventQueue[i];
354         
355                         if (queueItem) {
356                             queueItemKey = queueItem[0];
357                             queueItemValue = queueItem[1];
358
359                             if (queueItemKey == key) {
360     
361                                 /*
362                                     found a dupe... push to end of queue, null 
363                                     current item, and break
364                                 */
365     
366                                 this.eventQueue[i] = null;
367     
368                                 this.eventQueue.push(
369                                     [key, (!Lang.isUndefined(value) ? 
370                                     value : queueItemValue)]);
371     
372                                 foundDuplicate = true;
373                                 break;
374                             }
375                         }
376                     }
377                     
378                     // this is a refire, or a new property in the queue
379     
380                     if (! foundDuplicate && !Lang.isUndefined(value)) { 
381                         this.eventQueue.push([key, value]);
382                     }
383                 }
384         
385                 if (property.supercedes) {
386
387                     sLen = property.supercedes.length;
388
389                     for (s = 0; s < sLen; s++) {
390
391                         supercedesCheck = property.supercedes[s];
392                         qLen = this.eventQueue.length;
393
394                         for (q = 0; q < qLen; q++) {
395                             queueItemCheck = this.eventQueue[q];
396
397                             if (queueItemCheck) {
398                                 queueItemCheckKey = queueItemCheck[0];
399                                 queueItemCheckValue = queueItemCheck[1];
400
401                                 if (queueItemCheckKey == 
402                                     supercedesCheck.toLowerCase() ) {
403
404                                     this.eventQueue.push([queueItemCheckKey, 
405                                         queueItemCheckValue]);
406
407                                     this.eventQueue[q] = null;
408                                     break;
409
410                                 }
411                             }
412                         }
413                     }
414                 }
415
416                 YAHOO.log("Config event queue: " + this.outputEventQueue(), "info", "Config");
417
418                 return true;
419             } else {
420                 return false;
421             }
422         },
423         
424         /**
425         * Fires the event for a property using the property's current value.
426         * @method refireEvent
427         * @param {String} key The name of the property
428         */
429         refireEvent: function (key) {
430     
431             key = key.toLowerCase();
432         
433             var property = this.config[key];
434     
435             if (property && property.event && 
436     
437                 !Lang.isUndefined(property.value)) {
438     
439                 if (this.queueInProgress) {
440     
441                     this.queueProperty(key);
442     
443                 } else {
444     
445                     this.fireEvent(key, property.value);
446     
447                 }
448     
449             }
450         },
451         
452         /**
453         * Applies a key-value Object literal to the configuration, replacing  
454         * any existing values, and queueing the property events.
455         * Although the values will be set, fireQueue() must be called for their 
456         * associated events to execute.
457         * @method applyConfig
458         * @param {Object} userConfig The configuration Object literal
459         * @param {Boolean} init  When set to true, the initialConfig will 
460         * be set to the userConfig passed in, so that calling a reset will 
461         * reset the properties to the passed values.
462         */
463         applyConfig: function (userConfig, init) {
464         
465             var sKey,
466                 oConfig;
467
468             if (init) {
469                 oConfig = {};
470                 for (sKey in userConfig) {
471                     if (Lang.hasOwnProperty(userConfig, sKey)) {
472                         oConfig[sKey.toLowerCase()] = userConfig[sKey];
473                     }
474                 }
475                 this.initialConfig = oConfig;
476             }
477
478             for (sKey in userConfig) {
479                 if (Lang.hasOwnProperty(userConfig, sKey)) {
480                     this.queueProperty(sKey, userConfig[sKey]);
481                 }
482             }
483         },
484         
485         /**
486         * Refires the events for all configuration properties using their 
487         * current values.
488         * @method refresh
489         */
490         refresh: function () {
491
492             var prop;
493
494             for (prop in this.config) {
495                 if (Lang.hasOwnProperty(this.config, prop)) {
496                     this.refireEvent(prop);
497                 }
498             }
499         },
500         
501         /**
502         * Fires the normalized list of queued property change events
503         * @method fireQueue
504         */
505         fireQueue: function () {
506         
507             var i, 
508                 queueItem,
509                 key,
510                 value,
511                 property;
512         
513             this.queueInProgress = true;
514             for (i = 0;i < this.eventQueue.length; i++) {
515                 queueItem = this.eventQueue[i];
516                 if (queueItem) {
517         
518                     key = queueItem[0];
519                     value = queueItem[1];
520                     property = this.config[key];
521
522                     property.value = value;
523
524                     // Clear out queue entry, to avoid it being 
525                     // re-added to the queue by any queueProperty/supercedes
526                     // calls which are invoked during fireEvent
527                     this.eventQueue[i] = null;
528
529                     this.fireEvent(key,value);
530                 }
531             }
532             
533             this.queueInProgress = false;
534             this.eventQueue = [];
535         },
536         
537         /**
538         * Subscribes an external handler to the change event for any 
539         * given property. 
540         * @method subscribeToConfigEvent
541         * @param {String} key The property name
542         * @param {Function} handler The handler function to use subscribe to 
543         * the property's event
544         * @param {Object} obj The Object to use for scoping the event handler 
545         * (see CustomEvent documentation)
546         * @param {Boolean} overrideContext Optional. If true, will override
547         * "this" within the handler to map to the scope Object passed into the
548         * method.
549         * @return {Boolean} True, if the subscription was successful, 
550         * otherwise false.
551         */ 
552         subscribeToConfigEvent: function (key, handler, obj, overrideContext) {
553     
554             var property = this.config[key.toLowerCase()];
555     
556             if (property && property.event) {
557                 if (!Config.alreadySubscribed(property.event, handler, obj)) {
558                     property.event.subscribe(handler, obj, overrideContext);
559                 }
560                 return true;
561             } else {
562                 return false;
563             }
564     
565         },
566         
567         /**
568         * Unsubscribes an external handler from the change event for any 
569         * given property. 
570         * @method unsubscribeFromConfigEvent
571         * @param {String} key The property name
572         * @param {Function} handler The handler function to use subscribe to 
573         * the property's event
574         * @param {Object} obj The Object to use for scoping the event 
575         * handler (see CustomEvent documentation)
576         * @return {Boolean} True, if the unsubscription was successful, 
577         * otherwise false.
578         */
579         unsubscribeFromConfigEvent: function (key, handler, obj) {
580             var property = this.config[key.toLowerCase()];
581             if (property && property.event) {
582                 return property.event.unsubscribe(handler, obj);
583             } else {
584                 return false;
585             }
586         },
587         
588         /**
589         * Returns a string representation of the Config object
590         * @method toString
591         * @return {String} The Config object in string format.
592         */
593         toString: function () {
594             var output = "Config";
595             if (this.owner) {
596                 output += " [" + this.owner.toString() + "]";
597             }
598             return output;
599         },
600         
601         /**
602         * Returns a string representation of the Config object's current 
603         * CustomEvent queue
604         * @method outputEventQueue
605         * @return {String} The string list of CustomEvents currently queued 
606         * for execution
607         */
608         outputEventQueue: function () {
609
610             var output = "",
611                 queueItem,
612                 q,
613                 nQueue = this.eventQueue.length;
614               
615             for (q = 0; q < nQueue; q++) {
616                 queueItem = this.eventQueue[q];
617                 if (queueItem) {
618                     output += queueItem[0] + "=" + queueItem[1] + ", ";
619                 }
620             }
621             return output;
622         },
623
624         /**
625         * Sets all properties to null, unsubscribes all listeners from each 
626         * property's change event and all listeners from the configChangedEvent.
627         * @method destroy
628         */
629         destroy: function () {
630
631             var oConfig = this.config,
632                 sProperty,
633                 oProperty;
634
635
636             for (sProperty in oConfig) {
637             
638                 if (Lang.hasOwnProperty(oConfig, sProperty)) {
639
640                     oProperty = oConfig[sProperty];
641
642                     oProperty.event.unsubscribeAll();
643                     oProperty.event = null;
644
645                 }
646             
647             }
648             
649             this.configChangedEvent.unsubscribeAll();
650             
651             this.configChangedEvent = null;
652             this.owner = null;
653             this.config = null;
654             this.initialConfig = null;
655             this.eventQueue = null;
656         
657         }
658
659     };
660     
661     
662     
663     /**
664     * Checks to determine if a particular function/Object pair are already 
665     * subscribed to the specified CustomEvent
666     * @method YAHOO.util.Config.alreadySubscribed
667     * @static
668     * @param {YAHOO.util.CustomEvent} evt The CustomEvent for which to check 
669     * the subscriptions
670     * @param {Function} fn The function to look for in the subscribers list
671     * @param {Object} obj The execution scope Object for the subscription
672     * @return {Boolean} true, if the function/Object pair is already subscribed 
673     * to the CustomEvent passed in
674     */
675     Config.alreadySubscribed = function (evt, fn, obj) {
676     
677         var nSubscribers = evt.subscribers.length,
678             subsc,
679             i;
680
681         if (nSubscribers > 0) {
682             i = nSubscribers - 1;
683             do {
684                 subsc = evt.subscribers[i];
685                 if (subsc && subsc.obj == obj && subsc.fn == fn) {
686                     return true;
687                 }
688             }
689             while (i--);
690         }
691
692         return false;
693
694     };
695
696     YAHOO.lang.augmentProto(Config, YAHOO.util.EventProvider);
697
698 }());
699 /**
700 * The datemath module provides utility methods for basic JavaScript Date object manipulation and 
701 * comparison. 
702
703 * @module datemath
704 */
705
706 /**
707 * YAHOO.widget.DateMath is used for simple date manipulation. The class is a static utility
708 * used for adding, subtracting, and comparing dates.
709 * @namespace YAHOO.widget
710 * @class DateMath
711 */
712 YAHOO.widget.DateMath = {
713     /**
714     * Constant field representing Day
715     * @property DAY
716     * @static
717     * @final
718     * @type String
719     */
720     DAY : "D",
721
722     /**
723     * Constant field representing Week
724     * @property WEEK
725     * @static
726     * @final
727     * @type String
728     */
729     WEEK : "W",
730
731     /**
732     * Constant field representing Year
733     * @property YEAR
734     * @static
735     * @final
736     * @type String
737     */
738     YEAR : "Y",
739
740     /**
741     * Constant field representing Month
742     * @property MONTH
743     * @static
744     * @final
745     * @type String
746     */
747     MONTH : "M",
748
749     /**
750     * Constant field representing one day, in milliseconds
751     * @property ONE_DAY_MS
752     * @static
753     * @final
754     * @type Number
755     */
756     ONE_DAY_MS : 1000*60*60*24,
757     
758     /**
759      * Constant field representing the date in first week of January
760      * which identifies the first week of the year.
761      * <p>
762      * In the U.S, Jan 1st is normally used based on a Sunday start of week.
763      * ISO 8601, used widely throughout Europe, uses Jan 4th, based on a Monday start of week.
764      * </p>
765      * @property WEEK_ONE_JAN_DATE
766      * @static
767      * @type Number
768      */
769     WEEK_ONE_JAN_DATE : 1,
770
771     /**
772     * Adds the specified amount of time to the this instance.
773     * @method add
774     * @param {Date} date The JavaScript Date object to perform addition on
775     * @param {String} field The field constant to be used for performing addition.
776     * @param {Number} amount The number of units (measured in the field constant) to add to the date.
777     * @return {Date} The resulting Date object
778     */
779     add : function(date, field, amount) {
780         var d = new Date(date.getTime());
781         switch (field) {
782             case this.MONTH:
783                 var newMonth = date.getMonth() + amount;
784                 var years = 0;
785
786                 if (newMonth < 0) {
787                     while (newMonth < 0) {
788                         newMonth += 12;
789                         years -= 1;
790                     }
791                 } else if (newMonth > 11) {
792                     while (newMonth > 11) {
793                         newMonth -= 12;
794                         years += 1;
795                     }
796                 }
797
798                 d.setMonth(newMonth);
799                 d.setFullYear(date.getFullYear() + years);
800                 break;
801             case this.DAY:
802                 this._addDays(d, amount);
803                 // d.setDate(date.getDate() + amount);
804                 break;
805             case this.YEAR:
806                 d.setFullYear(date.getFullYear() + amount);
807                 break;
808             case this.WEEK:
809                 this._addDays(d, (amount * 7));
810                 // d.setDate(date.getDate() + (amount * 7));
811                 break;
812         }
813         return d;
814     },
815
816     /**
817      * Private helper method to account for bug in Safari 2 (webkit < 420)
818      * when Date.setDate(n) is called with n less than -128 or greater than 127.
819      * <p>
820      * Fix approach and original findings are available here:
821      * http://brianary.blogspot.com/2006/03/safari-date-bug.html
822      * </p>
823      * @method _addDays
824      * @param {Date} d JavaScript date object
825      * @param {Number} nDays The number of days to add to the date object (can be negative)
826      * @private
827      */
828     _addDays : function(d, nDays) {
829         if (YAHOO.env.ua.webkit && YAHOO.env.ua.webkit < 420) {
830             if (nDays < 0) {
831                 // Ensure we don't go below -128 (getDate() is always 1 to 31, so we won't go above 127)
832                 for(var min = -128; nDays < min; nDays -= min) {
833                     d.setDate(d.getDate() + min);
834                 }
835             } else {
836                 // Ensure we don't go above 96 + 31 = 127
837                 for(var max = 96; nDays > max; nDays -= max) {
838                     d.setDate(d.getDate() + max);
839                 }
840             }
841             // nDays should be remainder between -128 and 96
842         }
843         d.setDate(d.getDate() + nDays);
844     },
845
846     /**
847     * Subtracts the specified amount of time from the this instance.
848     * @method subtract
849     * @param {Date} date The JavaScript Date object to perform subtraction on
850     * @param {Number} field The this field constant to be used for performing subtraction.
851     * @param {Number} amount The number of units (measured in the field constant) to subtract from the date.
852     * @return {Date} The resulting Date object
853     */
854     subtract : function(date, field, amount) {
855         return this.add(date, field, (amount*-1));
856     },
857
858     /**
859     * Determines whether a given date is before another date on the calendar.
860     * @method before
861     * @param {Date} date  The Date object to compare with the compare argument
862     * @param {Date} compareTo The Date object to use for the comparison
863     * @return {Boolean} true if the date occurs before the compared date; false if not.
864     */
865     before : function(date, compareTo) {
866         var ms = compareTo.getTime();
867         if (date.getTime() < ms) {
868             return true;
869         } else {
870             return false;
871         }
872     },
873
874     /**
875     * Determines whether a given date is after another date on the calendar.
876     * @method after
877     * @param {Date} date  The Date object to compare with the compare argument
878     * @param {Date} compareTo The Date object to use for the comparison
879     * @return {Boolean} true if the date occurs after the compared date; false if not.
880     */
881     after : function(date, compareTo) {
882         var ms = compareTo.getTime();
883         if (date.getTime() > ms) {
884             return true;
885         } else {
886             return false;
887         }
888     },
889
890     /**
891     * Determines whether a given date is between two other dates on the calendar.
892     * @method between
893     * @param {Date} date  The date to check for
894     * @param {Date} dateBegin The start of the range
895     * @param {Date} dateEnd  The end of the range
896     * @return {Boolean} true if the date occurs between the compared dates; false if not.
897     */
898     between : function(date, dateBegin, dateEnd) {
899         if (this.after(date, dateBegin) && this.before(date, dateEnd)) {
900             return true;
901         } else {
902             return false;
903         }
904     },
905     
906     /**
907     * Retrieves a JavaScript Date object representing January 1 of any given year.
908     * @method getJan1
909     * @param {Number} calendarYear  The calendar year for which to retrieve January 1
910     * @return {Date} January 1 of the calendar year specified.
911     */
912     getJan1 : function(calendarYear) {
913         return this.getDate(calendarYear,0,1);
914     },
915
916     /**
917     * Calculates the number of days the specified date is from January 1 of the specified calendar year.
918     * Passing January 1 to this function would return an offset value of zero.
919     * @method getDayOffset
920     * @param {Date} date The JavaScript date for which to find the offset
921     * @param {Number} calendarYear The calendar year to use for determining the offset
922     * @return {Number} The number of days since January 1 of the given year
923     */
924     getDayOffset : function(date, calendarYear) {
925         var beginYear = this.getJan1(calendarYear); // Find the start of the year. This will be in week 1.
926         
927         // Find the number of days the passed in date is away from the calendar year start
928         var dayOffset = Math.ceil((date.getTime()-beginYear.getTime()) / this.ONE_DAY_MS);
929         return dayOffset;
930     },
931
932     /**
933     * Calculates the week number for the given date. Can currently support standard
934     * U.S. week numbers, based on Jan 1st defining the 1st week of the year, and 
935     * ISO8601 week numbers, based on Jan 4th defining the 1st week of the year.
936     * 
937     * @method getWeekNumber
938     * @param {Date} date The JavaScript date for which to find the week number
939     * @param {Number} firstDayOfWeek The index of the first day of the week (0 = Sun, 1 = Mon ... 6 = Sat).
940     * Defaults to 0
941     * @param {Number} janDate The date in the first week of January which defines week one for the year
942     * Defaults to the value of YAHOO.widget.DateMath.WEEK_ONE_JAN_DATE, which is 1 (Jan 1st). 
943     * For the U.S, this is normally Jan 1st. ISO8601 uses Jan 4th to define the first week of the year.
944     * 
945     * @return {Number} The number of the week containing the given date.
946     */
947     getWeekNumber : function(date, firstDayOfWeek, janDate) {
948
949         // Setup Defaults
950         firstDayOfWeek = firstDayOfWeek || 0;
951         janDate = janDate || this.WEEK_ONE_JAN_DATE;
952
953         var targetDate = this.clearTime(date),
954             startOfWeek,
955             endOfWeek;
956
957         if (targetDate.getDay() === firstDayOfWeek) { 
958             startOfWeek = targetDate;
959         } else {
960             startOfWeek = this.getFirstDayOfWeek(targetDate, firstDayOfWeek);
961         }
962
963         var startYear = startOfWeek.getFullYear();
964
965         // DST shouldn't be a problem here, math is quicker than setDate();
966         endOfWeek = new Date(startOfWeek.getTime() + 6*this.ONE_DAY_MS);
967
968         var weekNum;
969         if (startYear !== endOfWeek.getFullYear() && endOfWeek.getDate() >= janDate) {
970             // If years don't match, endOfWeek is in Jan. and if the 
971             // week has WEEK_ONE_JAN_DATE in it, it's week one by definition.
972             weekNum = 1;
973         } else {
974             // Get the 1st day of the 1st week, and 
975             // find how many days away we are from it.
976             var weekOne = this.clearTime(this.getDate(startYear, 0, janDate)),
977                 weekOneDayOne = this.getFirstDayOfWeek(weekOne, firstDayOfWeek);
978
979             // Round days to smoothen out 1 hr DST diff
980             var daysDiff  = Math.round((targetDate.getTime() - weekOneDayOne.getTime())/this.ONE_DAY_MS);
981
982             // Calc. Full Weeks
983             var rem = daysDiff % 7;
984             var weeksDiff = (daysDiff - rem)/7;
985             weekNum = weeksDiff + 1;
986         }
987         return weekNum;
988     },
989
990     /**
991      * Get the first day of the week, for the give date. 
992      * @param {Date} dt The date in the week for which the first day is required.
993      * @param {Number} startOfWeek The index for the first day of the week, 0 = Sun, 1 = Mon ... 6 = Sat (defaults to 0)
994      * @return {Date} The first day of the week
995      */
996     getFirstDayOfWeek : function (dt, startOfWeek) {
997         startOfWeek = startOfWeek || 0;
998         var dayOfWeekIndex = dt.getDay(),
999             dayOfWeek = (dayOfWeekIndex - startOfWeek + 7) % 7;
1000
1001         return this.subtract(dt, this.DAY, dayOfWeek);
1002     },
1003
1004     /**
1005     * Determines if a given week overlaps two different years.
1006     * @method isYearOverlapWeek
1007     * @param {Date} weekBeginDate The JavaScript Date representing the first day of the week.
1008     * @return {Boolean} true if the date overlaps two different years.
1009     */
1010     isYearOverlapWeek : function(weekBeginDate) {
1011         var overlaps = false;
1012         var nextWeek = this.add(weekBeginDate, this.DAY, 6);
1013         if (nextWeek.getFullYear() != weekBeginDate.getFullYear()) {
1014             overlaps = true;
1015         }
1016         return overlaps;
1017     },
1018
1019     /**
1020     * Determines if a given week overlaps two different months.
1021     * @method isMonthOverlapWeek
1022     * @param {Date} weekBeginDate The JavaScript Date representing the first day of the week.
1023     * @return {Boolean} true if the date overlaps two different months.
1024     */
1025     isMonthOverlapWeek : function(weekBeginDate) {
1026         var overlaps = false;
1027         var nextWeek = this.add(weekBeginDate, this.DAY, 6);
1028         if (nextWeek.getMonth() != weekBeginDate.getMonth()) {
1029             overlaps = true;
1030         }
1031         return overlaps;
1032     },
1033
1034     /**
1035     * Gets the first day of a month containing a given date.
1036     * @method findMonthStart
1037     * @param {Date} date The JavaScript Date used to calculate the month start
1038     * @return {Date}  The JavaScript Date representing the first day of the month
1039     */
1040     findMonthStart : function(date) {
1041         var start = this.getDate(date.getFullYear(), date.getMonth(), 1);
1042         return start;
1043     },
1044
1045     /**
1046     * Gets the last day of a month containing a given date.
1047     * @method findMonthEnd
1048     * @param {Date} date The JavaScript Date used to calculate the month end
1049     * @return {Date}  The JavaScript Date representing the last day of the month
1050     */
1051     findMonthEnd : function(date) {
1052         var start = this.findMonthStart(date);
1053         var nextMonth = this.add(start, this.MONTH, 1);
1054         var end = this.subtract(nextMonth, this.DAY, 1);
1055         return end;
1056     },
1057
1058     /**
1059     * Clears the time fields from a given date, effectively setting the time to 12 noon.
1060     * @method clearTime
1061     * @param {Date} date The JavaScript Date for which the time fields will be cleared
1062     * @return {Date}  The JavaScript Date cleared of all time fields
1063     */
1064     clearTime : function(date) {
1065         date.setHours(12,0,0,0);
1066         return date;
1067     },
1068
1069     /**
1070      * Returns a new JavaScript Date object, representing the given year, month and date. Time fields (hr, min, sec, ms) on the new Date object
1071      * 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 
1072      * set the year to 19xx if a year (xx) which is less than 100 is provided.
1073      * <p>
1074      * <em>NOTE:</em>Validation on argument values is not performed. It is the caller's responsibility to ensure
1075      * arguments are valid as per the ECMAScript-262 Date object specification for the new Date(year, month[, date]) constructor.
1076      * </p>
1077      * @method getDate
1078      * @param {Number} y Year.
1079      * @param {Number} m Month index from 0 (Jan) to 11 (Dec).
1080      * @param {Number} d (optional) Date from 1 to 31. If not provided, defaults to 1.
1081      * @return {Date} The JavaScript date object with year, month, date set as provided.
1082      */
1083     getDate : function(y, m, d) {
1084         var dt = null;
1085         if (YAHOO.lang.isUndefined(d)) {
1086             d = 1;
1087         }
1088         if (y >= 100) {
1089             dt = new Date(y, m, d);
1090         } else {
1091             dt = new Date();
1092             dt.setFullYear(y);
1093             dt.setMonth(m);
1094             dt.setDate(d);
1095             dt.setHours(0,0,0,0);
1096         }
1097         return dt;
1098     }
1099 };
1100 /**
1101 * 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
1102 * multi-month interface. Calendars are generated entirely via script and can be navigated without any page refreshes.
1103 * @module    calendar
1104 * @title    Calendar
1105 * @namespace  YAHOO.widget
1106 * @requires  yahoo,dom,event
1107 */
1108 (function(){
1109
1110     var Dom = YAHOO.util.Dom,
1111         Event = YAHOO.util.Event,
1112         Lang = YAHOO.lang,
1113         DateMath = YAHOO.widget.DateMath;
1114
1115 /**
1116 * Calendar is the base class for the Calendar widget. In its most basic
1117 * implementation, it has the ability to render a calendar widget on the page
1118 * that can be manipulated to select a single date, move back and forth between
1119 * months and years.
1120 * <p>To construct the placeholder for the calendar widget, the code is as
1121 * follows:
1122 *   <xmp>
1123 *       <div id="calContainer"></div>
1124 *   </xmp>
1125 * </p>
1126 * <p>
1127 * <strong>NOTE: As of 2.4.0, the constructor's ID argument is optional.</strong>
1128 * The Calendar can be constructed by simply providing a container ID string, 
1129 * or a reference to a container DIV HTMLElement (the element needs to exist 
1130 * in the document).
1131
1132 * E.g.:
1133 *   <xmp>
1134 *       var c = new YAHOO.widget.Calendar("calContainer", configOptions);
1135 *   </xmp>
1136 * or:
1137 *   <xmp>
1138 *       var containerDiv = YAHOO.util.Dom.get("calContainer");
1139 *       var c = new YAHOO.widget.Calendar(containerDiv, configOptions);
1140 *   </xmp>
1141 * </p>
1142 * <p>
1143 * If not provided, the ID will be generated from the container DIV ID by adding an "_t" suffix.
1144 * 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".
1145 * </p>
1146
1147 * @namespace YAHOO.widget
1148 * @class Calendar
1149 * @constructor
1150 * @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.
1151 * @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.
1152 * @param {Object} config optional The configuration object containing the initial configuration values for the Calendar.
1153 */
1154 function Calendar(id, containerId, config) {
1155     this.init.apply(this, arguments);
1156 }
1157
1158 /**
1159 * The path to be used for images loaded for the Calendar
1160 * @property YAHOO.widget.Calendar.IMG_ROOT
1161 * @static
1162 * @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
1163 * @type String
1164 */
1165 Calendar.IMG_ROOT = null;
1166
1167 /**
1168 * Type constant used for renderers to represent an individual date (M/D/Y)
1169 * @property YAHOO.widget.Calendar.DATE
1170 * @static
1171 * @final
1172 * @type String
1173 */
1174 Calendar.DATE = "D";
1175
1176 /**
1177 * Type constant used for renderers to represent an individual date across any year (M/D)
1178 * @property YAHOO.widget.Calendar.MONTH_DAY
1179 * @static
1180 * @final
1181 * @type String
1182 */
1183 Calendar.MONTH_DAY = "MD";
1184
1185 /**
1186 * Type constant used for renderers to represent a weekday
1187 * @property YAHOO.widget.Calendar.WEEKDAY
1188 * @static
1189 * @final
1190 * @type String
1191 */
1192 Calendar.WEEKDAY = "WD";
1193
1194 /**
1195 * Type constant used for renderers to represent a range of individual dates (M/D/Y-M/D/Y)
1196 * @property YAHOO.widget.Calendar.RANGE
1197 * @static
1198 * @final
1199 * @type String
1200 */
1201 Calendar.RANGE = "R";
1202
1203 /**
1204 * Type constant used for renderers to represent a month across any year
1205 * @property YAHOO.widget.Calendar.MONTH
1206 * @static
1207 * @final
1208 * @type String
1209 */
1210 Calendar.MONTH = "M";
1211
1212 /**
1213 * Constant that represents the total number of date cells that are displayed in a given month
1214 * @property YAHOO.widget.Calendar.DISPLAY_DAYS
1215 * @static
1216 * @final
1217 * @type Number
1218 */
1219 Calendar.DISPLAY_DAYS = 42;
1220
1221 /**
1222 * Constant used for halting the execution of the remainder of the render stack
1223 * @property YAHOO.widget.Calendar.STOP_RENDER
1224 * @static
1225 * @final
1226 * @type String
1227 */
1228 Calendar.STOP_RENDER = "S";
1229
1230 /**
1231 * Constant used to represent short date field string formats (e.g. Tu or Feb)
1232 * @property YAHOO.widget.Calendar.SHORT
1233 * @static
1234 * @final
1235 * @type String
1236 */
1237 Calendar.SHORT = "short";
1238
1239 /**
1240 * Constant used to represent long date field string formats (e.g. Monday or February)
1241 * @property YAHOO.widget.Calendar.LONG
1242 * @static
1243 * @final
1244 * @type String
1245 */
1246 Calendar.LONG = "long";
1247
1248 /**
1249 * Constant used to represent medium date field string formats (e.g. Mon)
1250 * @property YAHOO.widget.Calendar.MEDIUM
1251 * @static
1252 * @final
1253 * @type String
1254 */
1255 Calendar.MEDIUM = "medium";
1256
1257 /**
1258 * Constant used to represent single character date field string formats (e.g. M, T, W)
1259 * @property YAHOO.widget.Calendar.ONE_CHAR
1260 * @static
1261 * @final
1262 * @type String
1263 */
1264 Calendar.ONE_CHAR = "1char";
1265
1266 /**
1267 * The set of default Config property keys and values for the Calendar.
1268 *
1269 * <p>
1270 * NOTE: This property is made public in order to allow users to change 
1271 * the default values of configuration properties. Users should not 
1272 * modify the key string, unless they are overriding the Calendar implementation
1273 * </p>
1274 *
1275 * <p>
1276 * The property is an object with key/value pairs, the key being the 
1277 * uppercase configuration property name and the value being an object 
1278 * literal with a key string property, and a value property, specifying the 
1279 * default value of the property. To override a default value, you can set
1280 * the value property, for example, <code>YAHOO.widget.Calendar.DEFAULT_CONFIG.MULTI_SELECT.value = true;</code>
1281
1282 * @property YAHOO.widget.Calendar.DEFAULT_CONFIG
1283 * @static
1284 * @type Object
1285 */
1286
1287 Calendar.DEFAULT_CONFIG = {
1288     YEAR_OFFSET : {key:"year_offset", value:0, supercedes:["pagedate", "selected", "mindate","maxdate"]},
1289     TODAY : {key:"today", value:new Date(), supercedes:["pagedate"]}, 
1290     PAGEDATE : {key:"pagedate", value:null},
1291     SELECTED : {key:"selected", value:[]},
1292     TITLE : {key:"title", value:""},
1293     CLOSE : {key:"close", value:false},
1294     IFRAME : {key:"iframe", value:(YAHOO.env.ua.ie && YAHOO.env.ua.ie <= 6) ? true : false},
1295     MINDATE : {key:"mindate", value:null},
1296     MAXDATE : {key:"maxdate", value:null},
1297     MULTI_SELECT : {key:"multi_select", value:false},
1298     START_WEEKDAY : {key:"start_weekday", value:0},
1299     SHOW_WEEKDAYS : {key:"show_weekdays", value:true},
1300     SHOW_WEEK_HEADER : {key:"show_week_header", value:false},
1301     SHOW_WEEK_FOOTER : {key:"show_week_footer", value:false},
1302     HIDE_BLANK_WEEKS : {key:"hide_blank_weeks", value:false},
1303     NAV_ARROW_LEFT: {key:"nav_arrow_left", value:null} ,
1304     NAV_ARROW_RIGHT : {key:"nav_arrow_right", value:null} ,
1305     MONTHS_SHORT : {key:"months_short", value:["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]},
1306     MONTHS_LONG: {key:"months_long", value:["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]},
1307     WEEKDAYS_1CHAR: {key:"weekdays_1char", value:["S", "M", "T", "W", "T", "F", "S"]},
1308     WEEKDAYS_SHORT: {key:"weekdays_short", value:["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]},
1309     WEEKDAYS_MEDIUM: {key:"weekdays_medium", value:["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]},
1310     WEEKDAYS_LONG: {key:"weekdays_long", value:["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]},
1311     LOCALE_MONTHS:{key:"locale_months", value:"long"},
1312     LOCALE_WEEKDAYS:{key:"locale_weekdays", value:"short"},
1313     DATE_DELIMITER:{key:"date_delimiter", value:","},
1314     DATE_FIELD_DELIMITER:{key:"date_field_delimiter", value:"/"},
1315     DATE_RANGE_DELIMITER:{key:"date_range_delimiter", value:"-"},
1316     MY_MONTH_POSITION:{key:"my_month_position", value:1},
1317     MY_YEAR_POSITION:{key:"my_year_position", value:2},
1318     MD_MONTH_POSITION:{key:"md_month_position", value:1},
1319     MD_DAY_POSITION:{key:"md_day_position", value:2},
1320     MDY_MONTH_POSITION:{key:"mdy_month_position", value:1},
1321     MDY_DAY_POSITION:{key:"mdy_day_position", value:2},
1322     MDY_YEAR_POSITION:{key:"mdy_year_position", value:3},
1323     MY_LABEL_MONTH_POSITION:{key:"my_label_month_position", value:1},
1324     MY_LABEL_YEAR_POSITION:{key:"my_label_year_position", value:2},
1325     MY_LABEL_MONTH_SUFFIX:{key:"my_label_month_suffix", value:" "},
1326     MY_LABEL_YEAR_SUFFIX:{key:"my_label_year_suffix", value:""},
1327     NAV: {key:"navigator", value: null},
1328     STRINGS : { 
1329         key:"strings",
1330         value: {
1331             previousMonth : "Previous Month",
1332             nextMonth : "Next Month",
1333             close: "Close"
1334         },
1335         supercedes : ["close", "title"]
1336     }
1337 };
1338
1339 /**
1340 * The set of default Config property keys and values for the Calendar
1341 * @property YAHOO.widget.Calendar._DEFAULT_CONFIG
1342 * @deprecated Made public. See the public DEFAULT_CONFIG property for details
1343 * @final
1344 * @static
1345 * @private
1346 * @type Object
1347 */
1348 Calendar._DEFAULT_CONFIG = Calendar.DEFAULT_CONFIG;
1349
1350 var DEF_CFG = Calendar.DEFAULT_CONFIG;
1351
1352 /**
1353 * The set of Custom Event types supported by the Calendar
1354 * @property YAHOO.widget.Calendar._EVENT_TYPES
1355 * @final
1356 * @static
1357 * @private
1358 * @type Object
1359 */
1360 Calendar._EVENT_TYPES = {
1361     BEFORE_SELECT : "beforeSelect", 
1362     SELECT : "select",
1363     BEFORE_DESELECT : "beforeDeselect",
1364     DESELECT : "deselect",
1365     CHANGE_PAGE : "changePage",
1366     BEFORE_RENDER : "beforeRender",
1367     RENDER : "render",
1368     BEFORE_DESTROY : "beforeDestroy",
1369     DESTROY : "destroy",
1370     RESET : "reset",
1371     CLEAR : "clear",
1372     BEFORE_HIDE : "beforeHide",
1373     HIDE : "hide",
1374     BEFORE_SHOW : "beforeShow",
1375     SHOW : "show",
1376     BEFORE_HIDE_NAV : "beforeHideNav",
1377     HIDE_NAV : "hideNav",
1378     BEFORE_SHOW_NAV : "beforeShowNav",
1379     SHOW_NAV : "showNav",
1380     BEFORE_RENDER_NAV : "beforeRenderNav",
1381     RENDER_NAV : "renderNav"
1382 };
1383
1384 /**
1385 * The set of default style constants for the Calendar
1386 * @property YAHOO.widget.Calendar.STYLES
1387 * @static
1388 * @type Object An object with name/value pairs for the class name identifier/value.
1389 */
1390 Calendar.STYLES = {
1391     CSS_ROW_HEADER: "calrowhead",
1392     CSS_ROW_FOOTER: "calrowfoot",
1393     CSS_CELL : "calcell",
1394     CSS_CELL_SELECTOR : "selector",
1395     CSS_CELL_SELECTED : "selected",
1396     CSS_CELL_SELECTABLE : "selectable",
1397     CSS_CELL_RESTRICTED : "restricted",
1398     CSS_CELL_TODAY : "today",
1399     CSS_CELL_OOM : "oom",
1400     CSS_CELL_OOB : "previous",
1401     CSS_HEADER : "calheader",
1402     CSS_HEADER_TEXT : "calhead",
1403     CSS_BODY : "calbody",
1404     CSS_WEEKDAY_CELL : "calweekdaycell",
1405     CSS_WEEKDAY_ROW : "calweekdayrow",
1406     CSS_FOOTER : "calfoot",
1407     CSS_CALENDAR : "yui-calendar",
1408     CSS_SINGLE : "single",
1409     CSS_CONTAINER : "yui-calcontainer",
1410     CSS_NAV_LEFT : "calnavleft",
1411     CSS_NAV_RIGHT : "calnavright",
1412     CSS_NAV : "calnav",
1413     CSS_CLOSE : "calclose",
1414     CSS_CELL_TOP : "calcelltop",
1415     CSS_CELL_LEFT : "calcellleft",
1416     CSS_CELL_RIGHT : "calcellright",
1417     CSS_CELL_BOTTOM : "calcellbottom",
1418     CSS_CELL_HOVER : "calcellhover",
1419     CSS_CELL_HIGHLIGHT1 : "highlight1",
1420     CSS_CELL_HIGHLIGHT2 : "highlight2",
1421     CSS_CELL_HIGHLIGHT3 : "highlight3",
1422     CSS_CELL_HIGHLIGHT4 : "highlight4",
1423     CSS_WITH_TITLE: "withtitle",
1424     CSS_FIXED_SIZE: "fixedsize",
1425     CSS_LINK_CLOSE: "link-close"
1426 };
1427
1428 /**
1429 * The set of default style constants for the Calendar
1430 * @property YAHOO.widget.Calendar._STYLES
1431 * @deprecated Made public. See the public STYLES property for details
1432 * @final
1433 * @static
1434 * @private
1435 * @type Object
1436 */
1437 Calendar._STYLES = Calendar.STYLES;
1438
1439 Calendar.prototype = {
1440
1441     /**
1442     * The configuration object used to set up the calendars various locale and style options.
1443     * @property Config
1444     * @private
1445     * @deprecated Configuration properties should be set by calling Calendar.cfg.setProperty.
1446     * @type Object
1447     */
1448     Config : null,
1449
1450     /**
1451     * The parent CalendarGroup, only to be set explicitly by the parent group
1452     * @property parent
1453     * @type CalendarGroup
1454     */ 
1455     parent : null,
1456
1457     /**
1458     * The index of this item in the parent group
1459     * @property index
1460     * @type Number
1461     */
1462     index : -1,
1463
1464     /**
1465     * The collection of calendar table cells
1466     * @property cells
1467     * @type HTMLTableCellElement[]
1468     */
1469     cells : null,
1470
1471     /**
1472     * 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].
1473     * @property cellDates
1474     * @type Array[](Number[])
1475     */
1476     cellDates : null,
1477
1478     /**
1479     * The id that uniquely identifies this Calendar.
1480     * @property id
1481     * @type String
1482     */
1483     id : null,
1484
1485     /**
1486     * The unique id associated with the Calendar's container
1487     * @property containerId
1488     * @type String
1489     */
1490     containerId: null,
1491
1492     /**
1493     * 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.
1494     * @property oDomContainer
1495     * @type HTMLElement
1496     */
1497     oDomContainer : null,
1498
1499     /**
1500     * A Date object representing today's date.
1501     * @deprecated Use the "today" configuration property
1502     * @property today
1503     * @type Date
1504     */
1505     today : null,
1506
1507     /**
1508     * The list of render functions, along with required parameters, used to render cells. 
1509     * @property renderStack
1510     * @type Array[]
1511     */
1512     renderStack : null,
1513
1514     /**
1515     * A copy of the initial render functions created before rendering.
1516     * @property _renderStack
1517     * @private
1518     * @type Array
1519     */
1520     _renderStack : null,
1521
1522     /**
1523     * A reference to the CalendarNavigator instance created for this Calendar.
1524     * Will be null if the "navigator" configuration property has not been set
1525     * @property oNavigator
1526     * @type CalendarNavigator
1527     */
1528     oNavigator : null,
1529
1530     /**
1531     * The private list of initially selected dates.
1532     * @property _selectedDates
1533     * @private
1534     * @type Array
1535     */
1536     _selectedDates : null,
1537
1538     /**
1539     * A map of DOM event handlers to attach to cells associated with specific CSS class names
1540     * @property domEventMap
1541     * @type Object
1542     */
1543     domEventMap : null,
1544
1545     /**
1546      * Protected helper used to parse Calendar constructor/init arguments.
1547      *
1548      * As of 2.4.0, Calendar supports a simpler constructor 
1549      * signature. This method reconciles arguments
1550      * received in the pre 2.4.0 and 2.4.0 formats.
1551      * 
1552      * @protected
1553      * @method _parseArgs
1554      * @param {Array} Function "arguments" array
1555      * @return {Object} Object with id, container, config properties containing
1556      * the reconciled argument values.
1557      **/
1558     _parseArgs : function(args) {
1559         /*
1560            2.4.0 Constructors signatures
1561
1562            new Calendar(String)
1563            new Calendar(HTMLElement)
1564            new Calendar(String, ConfigObject)
1565            new Calendar(HTMLElement, ConfigObject)
1566
1567            Pre 2.4.0 Constructor signatures
1568
1569            new Calendar(String, String)
1570            new Calendar(String, HTMLElement)
1571            new Calendar(String, String, ConfigObject)
1572            new Calendar(String, HTMLElement, ConfigObject)
1573          */
1574         var nArgs = {id:null, container:null, config:null};
1575
1576         if (args && args.length && args.length > 0) {
1577             switch (args.length) {
1578                 case 1:
1579                     nArgs.id = null;
1580                     nArgs.container = args[0];
1581                     nArgs.config = null;
1582                     break;
1583                 case 2:
1584                     if (Lang.isObject(args[1]) && !args[1].tagName && !(args[1] instanceof String)) {
1585                         nArgs.id = null;
1586                         nArgs.container = args[0];
1587                         nArgs.config = args[1];
1588                     } else {
1589                         nArgs.id = args[0];
1590                         nArgs.container = args[1];
1591                         nArgs.config = null;
1592                     }
1593                     break;
1594                 default: // 3+
1595                     nArgs.id = args[0];
1596                     nArgs.container = args[1];
1597                     nArgs.config = args[2];
1598                     break;
1599             }
1600         } else {
1601             this.logger.log("Invalid constructor/init arguments", "error");
1602         }
1603         return nArgs;
1604     },
1605
1606     /**
1607     * Initializes the Calendar widget.
1608     * @method init
1609     *
1610     * @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.
1611     * @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.
1612     * @param {Object} config optional The configuration object containing the initial configuration values for the Calendar.
1613     */
1614     init : function(id, container, config) {
1615         // Normalize 2.4.0, pre 2.4.0 args
1616         var nArgs = this._parseArgs(arguments);
1617
1618         id = nArgs.id;
1619         container = nArgs.container;
1620         config = nArgs.config;
1621
1622         this.oDomContainer = Dom.get(container);
1623         if (!this.oDomContainer) { this.logger.log("Container not found in document.", "error"); }
1624
1625         if (!this.oDomContainer.id) {
1626             this.oDomContainer.id = Dom.generateId();
1627         }
1628         if (!id) {
1629             id = this.oDomContainer.id + "_t";
1630         }
1631
1632         this.id = id;
1633         this.containerId = this.oDomContainer.id;
1634
1635         this.logger = new YAHOO.widget.LogWriter("Calendar " + this.id);
1636         this.initEvents();
1637
1638         /**
1639         * The Config object used to hold the configuration variables for the Calendar
1640         * @property cfg
1641         * @type YAHOO.util.Config
1642         */
1643         this.cfg = new YAHOO.util.Config(this);
1644
1645         /**
1646         * The local object which contains the Calendar's options
1647         * @property Options
1648         * @type Object
1649         */
1650         this.Options = {};
1651
1652         /**
1653         * The local object which contains the Calendar's locale settings
1654         * @property Locale
1655         * @type Object
1656         */
1657         this.Locale = {};
1658
1659         this.initStyles();
1660
1661         Dom.addClass(this.oDomContainer, this.Style.CSS_CONTAINER);
1662         Dom.addClass(this.oDomContainer, this.Style.CSS_SINGLE);
1663
1664         this.cellDates = [];
1665         this.cells = [];
1666         this.renderStack = [];
1667         this._renderStack = [];
1668
1669         this.setupConfig();
1670
1671         if (config) {
1672             this.cfg.applyConfig(config, true);
1673         }
1674
1675         this.cfg.fireQueue();
1676
1677         this.today = this.cfg.getProperty("today");
1678     },
1679
1680     /**
1681     * Default Config listener for the iframe property. If the iframe config property is set to true, 
1682     * renders the built-in IFRAME shim if the container is relatively or absolutely positioned.
1683     * 
1684     * @method configIframe
1685     */
1686     configIframe : function(type, args, obj) {
1687         var useIframe = args[0];
1688     
1689         if (!this.parent) {
1690             if (Dom.inDocument(this.oDomContainer)) {
1691                 if (useIframe) {
1692                     var pos = Dom.getStyle(this.oDomContainer, "position");
1693                     
1694                     if (pos == "absolute" || pos == "relative") {
1695                         
1696                         if (!Dom.inDocument(this.iframe)) {
1697                             this.iframe = document.createElement("iframe");
1698                             this.iframe.src = "javascript:false;";
1699     
1700                             Dom.setStyle(this.iframe, "opacity", "0");
1701     
1702                             if (YAHOO.env.ua.ie && YAHOO.env.ua.ie <= 6) {
1703                                 Dom.addClass(this.iframe, this.Style.CSS_FIXED_SIZE);
1704                             }
1705     
1706                             this.oDomContainer.insertBefore(this.iframe, this.oDomContainer.firstChild);
1707                         }
1708                     }
1709                 } else {
1710                     if (this.iframe) {
1711                         if (this.iframe.parentNode) {
1712                             this.iframe.parentNode.removeChild(this.iframe);
1713                         }
1714                         this.iframe = null;
1715                     }
1716                 }
1717             }
1718         }
1719     },
1720
1721     /**
1722     * Default handler for the "title" property
1723     * @method configTitle
1724     */
1725     configTitle : function(type, args, obj) {
1726         var title = args[0];
1727
1728         // "" disables title bar
1729         if (title) {
1730             this.createTitleBar(title);
1731         } else {
1732             var close = this.cfg.getProperty(DEF_CFG.CLOSE.key);
1733             if (!close) {
1734                 this.removeTitleBar();
1735             } else {
1736                 this.createTitleBar("&#160;");
1737             }
1738         }
1739     },
1740     
1741     /**
1742     * Default handler for the "close" property
1743     * @method configClose
1744     */
1745     configClose : function(type, args, obj) {
1746         var close = args[0],
1747             title = this.cfg.getProperty(DEF_CFG.TITLE.key);
1748     
1749         if (close) {
1750             if (!title) {
1751                 this.createTitleBar("&#160;");
1752             }
1753             this.createCloseButton();
1754         } else {
1755             this.removeCloseButton();
1756             if (!title) {
1757                 this.removeTitleBar();
1758             }
1759         }
1760     },
1761
1762     /**
1763     * Initializes Calendar's built-in CustomEvents
1764     * @method initEvents
1765     */
1766     initEvents : function() {
1767
1768         var defEvents = Calendar._EVENT_TYPES,
1769             CE = YAHOO.util.CustomEvent,
1770             cal = this; // To help with minification
1771
1772         /**
1773         * Fired before a date selection is made
1774         * @event beforeSelectEvent
1775         */
1776         cal.beforeSelectEvent = new CE(defEvents.BEFORE_SELECT); 
1777
1778         /**
1779         * Fired when a date selection is made
1780         * @event selectEvent
1781         * @param {Array} Array of Date field arrays in the format [YYYY, MM, DD].
1782         */
1783         cal.selectEvent = new CE(defEvents.SELECT);
1784
1785         /**
1786         * Fired before a date or set of dates is deselected
1787         * @event beforeDeselectEvent
1788         */
1789         cal.beforeDeselectEvent = new CE(defEvents.BEFORE_DESELECT);
1790
1791         /**
1792         * Fired when a date or set of dates is deselected
1793         * @event deselectEvent
1794         * @param {Array} Array of Date field arrays in the format [YYYY, MM, DD].
1795         */
1796         cal.deselectEvent = new CE(defEvents.DESELECT);
1797     
1798         /**
1799         * Fired when the Calendar page is changed
1800         * @event changePageEvent
1801         * @param {Date} prevDate The date before the page was changed
1802         * @param {Date} newDate The date after the page was changed
1803         */
1804         cal.changePageEvent = new CE(defEvents.CHANGE_PAGE);
1805     
1806         /**
1807         * Fired before the Calendar is rendered
1808         * @event beforeRenderEvent
1809         */
1810         cal.beforeRenderEvent = new CE(defEvents.BEFORE_RENDER);
1811     
1812         /**
1813         * Fired when the Calendar is rendered
1814         * @event renderEvent
1815         */
1816         cal.renderEvent = new CE(defEvents.RENDER);
1817
1818         /**
1819         * Fired just before the Calendar is to be destroyed
1820         * @event beforeDestroyEvent
1821         */
1822         cal.beforeDestroyEvent = new CE(defEvents.BEFORE_DESTROY);
1823
1824         /**
1825         * Fired after the Calendar is destroyed. This event should be used
1826         * for notification only. When this event is fired, important Calendar instance
1827         * properties, dom references and event listeners have already been 
1828         * removed/dereferenced, and hence the Calendar instance is not in a usable 
1829         * state.
1830         *
1831         * @event destroyEvent
1832         */
1833         cal.destroyEvent = new CE(defEvents.DESTROY);
1834
1835         /**
1836         * Fired when the Calendar is reset
1837         * @event resetEvent
1838         */
1839         cal.resetEvent = new CE(defEvents.RESET);
1840
1841         /**
1842         * Fired when the Calendar is cleared
1843         * @event clearEvent
1844         */
1845         cal.clearEvent = new CE(defEvents.CLEAR);
1846
1847         /**
1848         * Fired just before the Calendar is to be shown
1849         * @event beforeShowEvent
1850         */
1851         cal.beforeShowEvent = new CE(defEvents.BEFORE_SHOW);
1852
1853         /**
1854         * Fired after the Calendar is shown
1855         * @event showEvent
1856         */
1857         cal.showEvent = new CE(defEvents.SHOW);
1858
1859         /**
1860         * Fired just before the Calendar is to be hidden
1861         * @event beforeHideEvent
1862         */
1863         cal.beforeHideEvent = new CE(defEvents.BEFORE_HIDE);
1864
1865         /**
1866         * Fired after the Calendar is hidden
1867         * @event hideEvent
1868         */
1869         cal.hideEvent = new CE(defEvents.HIDE);
1870
1871         /**
1872         * Fired just before the CalendarNavigator is to be shown
1873         * @event beforeShowNavEvent
1874         */
1875         cal.beforeShowNavEvent = new CE(defEvents.BEFORE_SHOW_NAV);
1876     
1877         /**
1878         * Fired after the CalendarNavigator is shown
1879         * @event showNavEvent
1880         */
1881         cal.showNavEvent = new CE(defEvents.SHOW_NAV);
1882     
1883         /**
1884         * Fired just before the CalendarNavigator is to be hidden
1885         * @event beforeHideNavEvent
1886         */
1887         cal.beforeHideNavEvent = new CE(defEvents.BEFORE_HIDE_NAV);
1888     
1889         /**
1890         * Fired after the CalendarNavigator is hidden
1891         * @event hideNavEvent
1892         */
1893         cal.hideNavEvent = new CE(defEvents.HIDE_NAV);
1894
1895         /**
1896         * Fired just before the CalendarNavigator is to be rendered
1897         * @event beforeRenderNavEvent
1898         */
1899         cal.beforeRenderNavEvent = new CE(defEvents.BEFORE_RENDER_NAV);
1900
1901         /**
1902         * Fired after the CalendarNavigator is rendered
1903         * @event renderNavEvent
1904         */
1905         cal.renderNavEvent = new CE(defEvents.RENDER_NAV);
1906
1907         cal.beforeSelectEvent.subscribe(cal.onBeforeSelect, this, true);
1908         cal.selectEvent.subscribe(cal.onSelect, this, true);
1909         cal.beforeDeselectEvent.subscribe(cal.onBeforeDeselect, this, true);
1910         cal.deselectEvent.subscribe(cal.onDeselect, this, true);
1911         cal.changePageEvent.subscribe(cal.onChangePage, this, true);
1912         cal.renderEvent.subscribe(cal.onRender, this, true);
1913         cal.resetEvent.subscribe(cal.onReset, this, true);
1914         cal.clearEvent.subscribe(cal.onClear, this, true);
1915     },
1916
1917     /**
1918     * The default event handler for clicks on the "Previous Month" navigation UI
1919     *
1920     * @method doPreviousMonthNav
1921     * @param {DOMEvent} e The DOM event
1922     * @param {Calendar} cal A reference to the calendar
1923     */
1924     doPreviousMonthNav : function(e, cal) {
1925         Event.preventDefault(e);
1926         // previousMonth invoked in a timeout, to allow
1927         // event to bubble up, with correct target. Calling
1928         // previousMonth, will call render which will remove 
1929         // HTML which generated the event, resulting in an 
1930         // invalid event target in certain browsers.
1931         setTimeout(function() {
1932             cal.previousMonth();
1933             var navs = Dom.getElementsByClassName(cal.Style.CSS_NAV_LEFT, "a", cal.oDomContainer);
1934             if (navs && navs[0]) {
1935                 try {
1936                     navs[0].focus();
1937                 } catch (ex) {
1938                     // ignore
1939                 }
1940             }
1941         }, 0);
1942     },
1943
1944     /**
1945      * The default event handler for clicks on the "Next Month" navigation UI
1946      *
1947      * @method doNextMonthNav
1948      * @param {DOMEvent} e The DOM event
1949      * @param {Calendar} cal A reference to the calendar
1950      */
1951     doNextMonthNav : function(e, cal) {
1952         Event.preventDefault(e);
1953         setTimeout(function() {
1954             cal.nextMonth();
1955             var navs = Dom.getElementsByClassName(cal.Style.CSS_NAV_RIGHT, "a", cal.oDomContainer);
1956             if (navs && navs[0]) {
1957                 try {
1958                     navs[0].focus();
1959                 } catch (ex) {
1960                     // ignore
1961                 }
1962             }
1963         }, 0);
1964     },
1965
1966     /**
1967     * The default event handler for date cell selection. Currently attached to 
1968     * the Calendar's bounding box, referenced by it's <a href="#property_oDomContainer">oDomContainer</a> property.
1969     *
1970     * @method doSelectCell
1971     * @param {DOMEvent} e The DOM event
1972     * @param {Calendar} cal A reference to the calendar
1973     */
1974     doSelectCell : function(e, cal) {
1975         var cell, d, date, index;
1976
1977         var target = Event.getTarget(e),
1978             tagName = target.tagName.toLowerCase(),
1979             defSelector = false;
1980
1981         while (tagName != "td" && !Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
1982
1983             if (!defSelector && tagName == "a" && Dom.hasClass(target, cal.Style.CSS_CELL_SELECTOR)) {
1984                 defSelector = true;
1985             }
1986
1987             target = target.parentNode;
1988             tagName = target.tagName.toLowerCase();
1989
1990             if (target == this.oDomContainer || tagName == "html") {
1991                 return;
1992             }
1993         }
1994
1995         if (defSelector) {
1996             // Stop link href navigation for default renderer
1997             Event.preventDefault(e);
1998         }
1999     
2000         cell = target;
2001
2002         if (Dom.hasClass(cell, cal.Style.CSS_CELL_SELECTABLE)) {
2003             index = cal.getIndexFromId(cell.id);
2004             if (index > -1) {
2005                 d = cal.cellDates[index];
2006                 if (d) {
2007                     date = DateMath.getDate(d[0],d[1]-1,d[2]);
2008                 
2009                     var link;
2010
2011                     cal.logger.log("Selecting cell " + index + " via click", "info");
2012                     if (cal.Options.MULTI_SELECT) {
2013                         link = cell.getElementsByTagName("a")[0];
2014                         if (link) {
2015                             link.blur();
2016                         }
2017
2018                         var cellDate = cal.cellDates[index];
2019                         var cellDateIndex = cal._indexOfSelectedFieldArray(cellDate);
2020
2021                         if (cellDateIndex > -1) { 
2022                             cal.deselectCell(index);
2023                         } else {
2024                             cal.selectCell(index);
2025                         } 
2026
2027                     } else {
2028                         link = cell.getElementsByTagName("a")[0];
2029                         if (link) {
2030                             link.blur();
2031                         }
2032                         cal.selectCell(index);
2033                     }
2034                 }
2035             }
2036         }
2037     },
2038
2039     /**
2040     * The event that is executed when the user hovers over a cell
2041     * @method doCellMouseOver
2042     * @param {DOMEvent} e The event
2043     * @param {Calendar} cal A reference to the calendar passed by the Event utility
2044     */
2045     doCellMouseOver : function(e, cal) {
2046         var target;
2047         if (e) {
2048             target = Event.getTarget(e);
2049         } else {
2050             target = this;
2051         }
2052
2053         while (target.tagName && target.tagName.toLowerCase() != "td") {
2054             target = target.parentNode;
2055             if (!target.tagName || target.tagName.toLowerCase() == "html") {
2056                 return;
2057             }
2058         }
2059
2060         if (Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
2061             Dom.addClass(target, cal.Style.CSS_CELL_HOVER);
2062         }
2063     },
2064
2065     /**
2066     * The event that is executed when the user moves the mouse out of a cell
2067     * @method doCellMouseOut
2068     * @param {DOMEvent} e The event
2069     * @param {Calendar} cal A reference to the calendar passed by the Event utility
2070     */
2071     doCellMouseOut : function(e, cal) {
2072         var target;
2073         if (e) {
2074             target = Event.getTarget(e);
2075         } else {
2076             target = this;
2077         }
2078
2079         while (target.tagName && target.tagName.toLowerCase() != "td") {
2080             target = target.parentNode;
2081             if (!target.tagName || target.tagName.toLowerCase() == "html") {
2082                 return;
2083             }
2084         }
2085
2086         if (Dom.hasClass(target, cal.Style.CSS_CELL_SELECTABLE)) {
2087             Dom.removeClass(target, cal.Style.CSS_CELL_HOVER);
2088         }
2089     },
2090
2091     setupConfig : function() {
2092
2093         var cfg = this.cfg;
2094
2095         /**
2096         * The date to use to represent "Today".
2097         *
2098         * @config today
2099         * @type Date
2100         * @default The client side date (new Date()) when the Calendar is instantiated.
2101         */
2102         cfg.addProperty(DEF_CFG.TODAY.key, { value: new Date(DEF_CFG.TODAY.value.getTime()), supercedes:DEF_CFG.TODAY.supercedes, handler:this.configToday, suppressEvent:true } );
2103
2104         /**
2105         * The month/year representing the current visible Calendar date (mm/yyyy)
2106         * @config pagedate
2107         * @type String | Date
2108         * @default Today's date
2109         */
2110         cfg.addProperty(DEF_CFG.PAGEDATE.key, { value: DEF_CFG.PAGEDATE.value || new Date(DEF_CFG.TODAY.value.getTime()), handler:this.configPageDate } );
2111
2112         /**
2113         * The date or range of dates representing the current Calendar selection
2114         * @config selected
2115         * @type String
2116         * @default []
2117         */
2118         cfg.addProperty(DEF_CFG.SELECTED.key, { value:DEF_CFG.SELECTED.value.concat(), handler:this.configSelected } );
2119
2120         /**
2121         * The title to display above the Calendar's month header
2122         * @config title
2123         * @type String
2124         * @default ""
2125         */
2126         cfg.addProperty(DEF_CFG.TITLE.key, { value:DEF_CFG.TITLE.value, handler:this.configTitle } );
2127
2128         /**
2129         * Whether or not a close button should be displayed for this Calendar
2130         * @config close
2131         * @type Boolean
2132         * @default false
2133         */
2134         cfg.addProperty(DEF_CFG.CLOSE.key, { value:DEF_CFG.CLOSE.value, handler:this.configClose } );
2135
2136         /**
2137         * 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.
2138         * This property is enabled by default for IE6 and below. It is disabled by default for other browsers for performance reasons, but can be 
2139         * enabled if required.
2140         * 
2141         * @config iframe
2142         * @type Boolean
2143         * @default true for IE6 and below, false for all other browsers
2144         */
2145         cfg.addProperty(DEF_CFG.IFRAME.key, { value:DEF_CFG.IFRAME.value, handler:this.configIframe, validator:cfg.checkBoolean } );
2146
2147         /**
2148         * The minimum selectable date in the current Calendar (mm/dd/yyyy)
2149         * @config mindate
2150         * @type String | Date
2151         * @default null
2152         */
2153         cfg.addProperty(DEF_CFG.MINDATE.key, { value:DEF_CFG.MINDATE.value, handler:this.configMinDate } );
2154
2155         /**
2156         * The maximum selectable date in the current Calendar (mm/dd/yyyy)
2157         * @config maxdate
2158         * @type String | Date
2159         * @default null
2160         */
2161         cfg.addProperty(DEF_CFG.MAXDATE.key, { value:DEF_CFG.MAXDATE.value, handler:this.configMaxDate } );
2162
2163         // Options properties
2164     
2165         /**
2166         * True if the Calendar should allow multiple selections. False by default.
2167         * @config MULTI_SELECT
2168         * @type Boolean
2169         * @default false
2170         */
2171         cfg.addProperty(DEF_CFG.MULTI_SELECT.key, { value:DEF_CFG.MULTI_SELECT.value, handler:this.configOptions, validator:cfg.checkBoolean } );
2172
2173         /**
2174         * The weekday the week begins on. Default is 0 (Sunday = 0, Monday = 1 ... Saturday = 6).
2175         * @config START_WEEKDAY
2176         * @type number
2177         * @default 0
2178         */
2179         cfg.addProperty(DEF_CFG.START_WEEKDAY.key, { value:DEF_CFG.START_WEEKDAY.value, handler:this.configOptions, validator:cfg.checkNumber  } );
2180     
2181         /**
2182         * True if the Calendar should show weekday labels. True by default.
2183         * @config SHOW_WEEKDAYS
2184         * @type Boolean
2185         * @default true
2186         */
2187         cfg.addProperty(DEF_CFG.SHOW_WEEKDAYS.key, { value:DEF_CFG.SHOW_WEEKDAYS.value, handler:this.configOptions, validator:cfg.checkBoolean  } );
2188     
2189         /**
2190         * True if the Calendar should show week row headers. False by default.
2191         * @config SHOW_WEEK_HEADER
2192         * @type Boolean
2193         * @default false
2194         */
2195         cfg.addProperty(DEF_CFG.SHOW_WEEK_HEADER.key, { value:DEF_CFG.SHOW_WEEK_HEADER.value, handler:this.configOptions, validator:cfg.checkBoolean } );
2196     
2197         /**
2198         * True if the Calendar should show week row footers. False by default.
2199         * @config SHOW_WEEK_FOOTER
2200         * @type Boolean
2201         * @default false
2202         */ 
2203         cfg.addProperty(DEF_CFG.SHOW_WEEK_FOOTER.key,{ value:DEF_CFG.SHOW_WEEK_FOOTER.value, handler:this.configOptions, validator:cfg.checkBoolean } );
2204     
2205         /**
2206         * True if the Calendar should suppress weeks that are not a part of the current month. False by default.
2207         * @config HIDE_BLANK_WEEKS
2208         * @type Boolean
2209         * @default false
2210         */ 
2211         cfg.addProperty(DEF_CFG.HIDE_BLANK_WEEKS.key, { value:DEF_CFG.HIDE_BLANK_WEEKS.value, handler:this.configOptions, validator:cfg.checkBoolean } );
2212         
2213         /**
2214         * The image that should be used for the left navigation arrow.
2215         * @config NAV_ARROW_LEFT
2216         * @type String
2217         * @deprecated You can customize the image by overriding the default CSS class for the left arrow - "calnavleft"  
2218         * @default null
2219         */ 
2220         cfg.addProperty(DEF_CFG.NAV_ARROW_LEFT.key, { value:DEF_CFG.NAV_ARROW_LEFT.value, handler:this.configOptions } );
2221     
2222         /**
2223         * The image that should be used for the right navigation arrow.
2224         * @config NAV_ARROW_RIGHT
2225         * @type String
2226         * @deprecated You can customize the image by overriding the default CSS class for the right arrow - "calnavright"
2227         * @default null
2228         */ 
2229         cfg.addProperty(DEF_CFG.NAV_ARROW_RIGHT.key, { value:DEF_CFG.NAV_ARROW_RIGHT.value, handler:this.configOptions } );
2230     
2231         // Locale properties
2232     
2233         /**
2234         * The short month labels for the current locale.
2235         * @config MONTHS_SHORT
2236         * @type String[]
2237         * @default ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
2238         */
2239         cfg.addProperty(DEF_CFG.MONTHS_SHORT.key, { value:DEF_CFG.MONTHS_SHORT.value, handler:this.configLocale } );
2240         
2241         /**
2242         * The long month labels for the current locale.
2243         * @config MONTHS_LONG
2244         * @type String[]
2245         * @default ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
2246         */ 
2247         cfg.addProperty(DEF_CFG.MONTHS_LONG.key,  { value:DEF_CFG.MONTHS_LONG.value, handler:this.configLocale } );
2248
2249         /**
2250         * The 1-character weekday labels for the current locale.
2251         * @config WEEKDAYS_1CHAR
2252         * @type String[]
2253         * @default ["S", "M", "T", "W", "T", "F", "S"]
2254         */ 
2255         cfg.addProperty(DEF_CFG.WEEKDAYS_1CHAR.key, { value:DEF_CFG.WEEKDAYS_1CHAR.value, handler:this.configLocale } );
2256         
2257         /**
2258         * The short weekday labels for the current locale.
2259         * @config WEEKDAYS_SHORT
2260         * @type String[]
2261         * @default ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
2262         */ 
2263         cfg.addProperty(DEF_CFG.WEEKDAYS_SHORT.key, { value:DEF_CFG.WEEKDAYS_SHORT.value, handler:this.configLocale } );
2264         
2265         /**
2266         * The medium weekday labels for the current locale.
2267         * @config WEEKDAYS_MEDIUM
2268         * @type String[]
2269         * @default ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
2270         */ 
2271         cfg.addProperty(DEF_CFG.WEEKDAYS_MEDIUM.key, { value:DEF_CFG.WEEKDAYS_MEDIUM.value, handler:this.configLocale } );
2272         
2273         /**
2274         * The long weekday labels for the current locale.
2275         * @config WEEKDAYS_LONG
2276         * @type String[]
2277         * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
2278         */ 
2279         cfg.addProperty(DEF_CFG.WEEKDAYS_LONG.key, { value:DEF_CFG.WEEKDAYS_LONG.value, handler:this.configLocale } );
2280
2281         /**
2282         * Refreshes the locale values used to build the Calendar.
2283         * @method refreshLocale
2284         * @private
2285         */
2286         var refreshLocale = function() {
2287             cfg.refireEvent(DEF_CFG.LOCALE_MONTHS.key);
2288             cfg.refireEvent(DEF_CFG.LOCALE_WEEKDAYS.key);
2289         };
2290     
2291         cfg.subscribeToConfigEvent(DEF_CFG.START_WEEKDAY.key, refreshLocale, this, true);
2292         cfg.subscribeToConfigEvent(DEF_CFG.MONTHS_SHORT.key, refreshLocale, this, true);
2293         cfg.subscribeToConfigEvent(DEF_CFG.MONTHS_LONG.key, refreshLocale, this, true);
2294         cfg.subscribeToConfigEvent(DEF_CFG.WEEKDAYS_1CHAR.key, refreshLocale, this, true);
2295         cfg.subscribeToConfigEvent(DEF_CFG.WEEKDAYS_SHORT.key, refreshLocale, this, true);
2296         cfg.subscribeToConfigEvent(DEF_CFG.WEEKDAYS_MEDIUM.key, refreshLocale, this, true);
2297         cfg.subscribeToConfigEvent(DEF_CFG.WEEKDAYS_LONG.key, refreshLocale, this, true);
2298        
2299         /**
2300         * The setting that determines which length of month labels should be used. Possible values are "short" and "long".
2301         * @config LOCALE_MONTHS
2302         * @type String
2303         * @default "long"
2304         */ 
2305         cfg.addProperty(DEF_CFG.LOCALE_MONTHS.key, { value:DEF_CFG.LOCALE_MONTHS.value, handler:this.configLocaleValues } );
2306         
2307         /**
2308         * The setting that determines which length of weekday labels should be used. Possible values are "1char", "short", "medium", and "long".
2309         * @config LOCALE_WEEKDAYS
2310         * @type String
2311         * @default "short"
2312         */ 
2313         cfg.addProperty(DEF_CFG.LOCALE_WEEKDAYS.key, { value:DEF_CFG.LOCALE_WEEKDAYS.value, handler:this.configLocaleValues } );
2314
2315         /**
2316         * The positive or negative year offset from the Gregorian calendar year (assuming a January 1st rollover) to 
2317         * be used when displaying and parsing dates. NOTE: All JS Date objects returned by methods, or expected as input by
2318         * methods will always represent the Gregorian year, in order to maintain date/month/week values. 
2319         *
2320         * @config YEAR_OFFSET
2321         * @type Number
2322         * @default 0
2323         */
2324         cfg.addProperty(DEF_CFG.YEAR_OFFSET.key, { value:DEF_CFG.YEAR_OFFSET.value, supercedes:DEF_CFG.YEAR_OFFSET.supercedes, handler:this.configLocale  } );
2325     
2326         /**
2327         * The value used to delimit individual dates in a date string passed to various Calendar functions.
2328         * @config DATE_DELIMITER
2329         * @type String
2330         * @default ","
2331         */ 
2332         cfg.addProperty(DEF_CFG.DATE_DELIMITER.key,  { value:DEF_CFG.DATE_DELIMITER.value, handler:this.configLocale } );
2333     
2334         /**
2335         * The value used to delimit date fields in a date string passed to various Calendar functions.
2336         * @config DATE_FIELD_DELIMITER
2337         * @type String
2338         * @default "/"
2339         */ 
2340         cfg.addProperty(DEF_CFG.DATE_FIELD_DELIMITER.key, { value:DEF_CFG.DATE_FIELD_DELIMITER.value, handler:this.configLocale } );
2341     
2342         /**
2343         * The value used to delimit date ranges in a date string passed to various Calendar functions.
2344         * @config DATE_RANGE_DELIMITER
2345         * @type String
2346         * @default "-"
2347         */
2348         cfg.addProperty(DEF_CFG.DATE_RANGE_DELIMITER.key, { value:DEF_CFG.DATE_RANGE_DELIMITER.value, handler:this.configLocale } );
2349     
2350         /**
2351         * The position of the month in a month/year date string
2352         * @config MY_MONTH_POSITION
2353         * @type Number
2354         * @default 1
2355         */
2356         cfg.addProperty(DEF_CFG.MY_MONTH_POSITION.key, { value:DEF_CFG.MY_MONTH_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
2357     
2358         /**
2359         * The position of the year in a month/year date string
2360         * @config MY_YEAR_POSITION
2361         * @type Number
2362         * @default 2
2363         */
2364         cfg.addProperty(DEF_CFG.MY_YEAR_POSITION.key, { value:DEF_CFG.MY_YEAR_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
2365     
2366         /**
2367         * The position of the month in a month/day date string
2368         * @config MD_MONTH_POSITION
2369         * @type Number
2370         * @default 1
2371         */
2372         cfg.addProperty(DEF_CFG.MD_MONTH_POSITION.key, { value:DEF_CFG.MD_MONTH_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
2373     
2374         /**
2375         * The position of the day in a month/year date string
2376         * @config MD_DAY_POSITION
2377         * @type Number
2378         * @default 2
2379         */
2380         cfg.addProperty(DEF_CFG.MD_DAY_POSITION.key,  { value:DEF_CFG.MD_DAY_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
2381     
2382         /**
2383         * The position of the month in a month/day/year date string
2384         * @config MDY_MONTH_POSITION
2385         * @type Number
2386         * @default 1
2387         */
2388         cfg.addProperty(DEF_CFG.MDY_MONTH_POSITION.key, { value:DEF_CFG.MDY_MONTH_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
2389     
2390         /**
2391         * The position of the day in a month/day/year date string
2392         * @config MDY_DAY_POSITION
2393         * @type Number
2394         * @default 2
2395         */
2396         cfg.addProperty(DEF_CFG.MDY_DAY_POSITION.key, { value:DEF_CFG.MDY_DAY_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
2397     
2398         /**
2399         * The position of the year in a month/day/year date string
2400         * @config MDY_YEAR_POSITION
2401         * @type Number
2402         * @default 3
2403         */
2404         cfg.addProperty(DEF_CFG.MDY_YEAR_POSITION.key, { value:DEF_CFG.MDY_YEAR_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
2405         
2406         /**
2407         * The position of the month in the month year label string used as the Calendar header
2408         * @config MY_LABEL_MONTH_POSITION
2409         * @type Number
2410         * @default 1
2411         */
2412         cfg.addProperty(DEF_CFG.MY_LABEL_MONTH_POSITION.key, { value:DEF_CFG.MY_LABEL_MONTH_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
2413     
2414         /**
2415         * The position of the year in the month year label string used as the Calendar header
2416         * @config MY_LABEL_YEAR_POSITION
2417         * @type Number
2418         * @default 2
2419         */
2420         cfg.addProperty(DEF_CFG.MY_LABEL_YEAR_POSITION.key, { value:DEF_CFG.MY_LABEL_YEAR_POSITION.value, handler:this.configLocale, validator:cfg.checkNumber } );
2421         
2422         /**
2423         * The suffix used after the month when rendering the Calendar header
2424         * @config MY_LABEL_MONTH_SUFFIX
2425         * @type String
2426         * @default " "
2427         */
2428         cfg.addProperty(DEF_CFG.MY_LABEL_MONTH_SUFFIX.key, { value:DEF_CFG.MY_LABEL_MONTH_SUFFIX.value, handler:this.configLocale } );
2429         
2430         /**
2431         * The suffix used after the year when rendering the Calendar header
2432         * @config MY_LABEL_YEAR_SUFFIX
2433         * @type String
2434         * @default ""
2435         */
2436         cfg.addProperty(DEF_CFG.MY_LABEL_YEAR_SUFFIX.key, { value:DEF_CFG.MY_LABEL_YEAR_SUFFIX.value, handler:this.configLocale } );
2437
2438         /**
2439         * Configuration for the Month/Year CalendarNavigator UI which allows the user to jump directly to a 
2440         * specific Month/Year without having to scroll sequentially through months.
2441         * <p>
2442         * Setting this property to null (default value) or false, will disable the CalendarNavigator UI.
2443         * </p>
2444         * <p>
2445         * Setting this property to true will enable the CalendarNavigatior UI with the default CalendarNavigator configuration values.
2446         * </p>
2447         * <p>
2448         * This property can also be set to an object literal containing configuration properties for the CalendarNavigator UI.
2449         * The configuration object expects the the following case-sensitive properties, with the "strings" property being a nested object.
2450         * Any properties which are not provided will use the default values (defined in the CalendarNavigator class).
2451         * </p>
2452         * <dl>
2453         * <dt>strings</dt>
2454         * <dd><em>Object</em> :  An object with the properties shown below, defining the string labels to use in the Navigator's UI
2455         *     <dl>
2456         *         <dt>month</dt><dd><em>String</em> : The string to use for the month label. Defaults to "Month".</dd>
2457         *         <dt>year</dt><dd><em>String</em> : The string to use for the year label. Defaults to "Year".</dd>
2458         *         <dt>submit</dt><dd><em>String</em> : The string to use for the submit button label. Defaults to "Okay".</dd>
2459         *         <dt>cancel</dt><dd><em>String</em> : The string to use for the cancel button label. Defaults to "Cancel".</dd>
2460         *         <dt>invalidYear</dt><dd><em>String</em> : The string to use for invalid year values. Defaults to "Year needs to be a number".</dd>
2461         *     </dl>
2462         * </dd>
2463         * <dt>monthFormat</dt><dd><em>String</em> : The month format to use. Either YAHOO.widget.Calendar.LONG, or YAHOO.widget.Calendar.SHORT. Defaults to YAHOO.widget.Calendar.LONG</dd>
2464         * <dt>initialFocus</dt><dd><em>String</em> : Either "year" or "month" specifying which input control should get initial focus. Defaults to "year"</dd>
2465         * </dl>
2466         * <p>E.g.</p>
2467         * <pre>
2468         * var navConfig = {
2469         *   strings: {
2470         *    month:"Calendar Month",
2471         *    year:"Calendar Year",
2472         *    submit: "Submit",
2473         *    cancel: "Cancel",
2474         *    invalidYear: "Please enter a valid year"
2475         *   },
2476         *   monthFormat: YAHOO.widget.Calendar.SHORT,
2477         *   initialFocus: "month"
2478         * }
2479         * </pre>
2480         * @config navigator
2481         * @type {Object|Boolean}
2482         * @default null
2483         */
2484         cfg.addProperty(DEF_CFG.NAV.key, { value:DEF_CFG.NAV.value, handler:this.configNavigator } );
2485
2486         /**
2487          * The map of UI strings which the Calendar UI uses.
2488          *
2489          * @config strings
2490          * @type {Object}
2491          * @default An object with the properties shown below:
2492          *     <dl>
2493          *         <dt>previousMonth</dt><dd><em>String</em> : The string to use for the "Previous Month" navigation UI. Defaults to "Previous Month".</dd>
2494          *         <dt>nextMonth</dt><dd><em>String</em> : The string to use for the "Next Month" navigation UI. Defaults to "Next Month".</dd>
2495          *         <dt>close</dt><dd><em>String</em> : The string to use for the close button label. Defaults to "Close".</dd>
2496          *     </dl>
2497          */
2498         cfg.addProperty(DEF_CFG.STRINGS.key, { 
2499             value:DEF_CFG.STRINGS.value,
2500             handler:this.configStrings,
2501             validator: function(val) {
2502                 return Lang.isObject(val);
2503             },
2504             supercedes:DEF_CFG.STRINGS.supercedes
2505         });
2506     },
2507
2508     /**
2509     * The default handler for the "strings" property
2510     * @method configStrings
2511     */
2512     configStrings : function(type, args, obj) {
2513         var val = Lang.merge(DEF_CFG.STRINGS.value, args[0]);
2514         this.cfg.setProperty(DEF_CFG.STRINGS.key, val, true);
2515     },
2516
2517     /**
2518     * The default handler for the "pagedate" property
2519     * @method configPageDate
2520     */
2521     configPageDate : function(type, args, obj) {
2522         this.cfg.setProperty(DEF_CFG.PAGEDATE.key, this._parsePageDate(args[0]), true);
2523     },
2524
2525     /**
2526     * The default handler for the "mindate" property
2527     * @method configMinDate
2528     */
2529     configMinDate : function(type, args, obj) {
2530         var val = args[0];
2531         if (Lang.isString(val)) {
2532             val = this._parseDate(val);
2533             this.cfg.setProperty(DEF_CFG.MINDATE.key, DateMath.getDate(val[0],(val[1]-1),val[2]));
2534         }
2535     },
2536
2537     /**
2538     * The default handler for the "maxdate" property
2539     * @method configMaxDate
2540     */
2541     configMaxDate : function(type, args, obj) {
2542         var val = args[0];
2543         if (Lang.isString(val)) {
2544             val = this._parseDate(val);
2545             this.cfg.setProperty(DEF_CFG.MAXDATE.key, DateMath.getDate(val[0],(val[1]-1),val[2]));
2546         }
2547     },
2548
2549     /**
2550     * The default handler for the "today" property
2551     * @method configToday
2552     */
2553     configToday : function(type, args, obj) {
2554         // Only do this for initial set. Changing the today property after the initial
2555         // set, doesn't affect pagedate
2556         var val = args[0];
2557         if (Lang.isString(val)) {
2558             val = this._parseDate(val);
2559         }
2560         var today = DateMath.clearTime(val);
2561         if (!this.cfg.initialConfig[DEF_CFG.PAGEDATE.key]) {
2562             this.cfg.setProperty(DEF_CFG.PAGEDATE.key, today);
2563         }
2564         this.today = today;
2565         this.cfg.setProperty(DEF_CFG.TODAY.key, today, true);
2566     },
2567
2568     /**
2569     * The default handler for the "selected" property
2570     * @method configSelected
2571     */
2572     configSelected : function(type, args, obj) {
2573         var selected = args[0],
2574             cfgSelected = DEF_CFG.SELECTED.key;
2575         
2576         if (selected) {
2577             if (Lang.isString(selected)) {
2578                 this.cfg.setProperty(cfgSelected, this._parseDates(selected), true);
2579             } 
2580         }
2581         if (! this._selectedDates) {
2582             this._selectedDates = this.cfg.getProperty(cfgSelected);
2583         }
2584     },
2585     
2586     /**
2587     * The default handler for all configuration options properties
2588     * @method configOptions
2589     */
2590     configOptions : function(type, args, obj) {
2591         this.Options[type.toUpperCase()] = args[0];
2592     },
2593
2594     /**
2595     * The default handler for all configuration locale properties
2596     * @method configLocale
2597     */
2598     configLocale : function(type, args, obj) {
2599         this.Locale[type.toUpperCase()] = args[0];
2600
2601         this.cfg.refireEvent(DEF_CFG.LOCALE_MONTHS.key);
2602         this.cfg.refireEvent(DEF_CFG.LOCALE_WEEKDAYS.key);
2603     },
2604     
2605     /**
2606     * The default handler for all configuration locale field length properties
2607     * @method configLocaleValues
2608     */
2609     configLocaleValues : function(type, args, obj) {
2610
2611         type = type.toLowerCase();
2612
2613         var val = args[0],
2614             cfg = this.cfg,
2615             Locale = this.Locale;
2616
2617         switch (type) {
2618             case DEF_CFG.LOCALE_MONTHS.key:
2619                 switch (val) {
2620                     case Calendar.SHORT:
2621                         Locale.LOCALE_MONTHS = cfg.getProperty(DEF_CFG.MONTHS_SHORT.key).concat();
2622                         break;
2623                     case Calendar.LONG:
2624                         Locale.LOCALE_MONTHS = cfg.getProperty(DEF_CFG.MONTHS_LONG.key).concat();
2625                         break;
2626                 }
2627                 break;
2628             case DEF_CFG.LOCALE_WEEKDAYS.key:
2629                 switch (val) {
2630                     case Calendar.ONE_CHAR:
2631                         Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_1CHAR.key).concat();
2632                         break;
2633                     case Calendar.SHORT:
2634                         Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_SHORT.key).concat();
2635                         break;
2636                     case Calendar.MEDIUM:
2637                         Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_MEDIUM.key).concat();
2638                         break;
2639                     case Calendar.LONG:
2640                         Locale.LOCALE_WEEKDAYS = cfg.getProperty(DEF_CFG.WEEKDAYS_LONG.key).concat();
2641                         break;
2642                 }
2643                 
2644                 var START_WEEKDAY = cfg.getProperty(DEF_CFG.START_WEEKDAY.key);
2645     
2646                 if (START_WEEKDAY > 0) {
2647                     for (var w=0; w < START_WEEKDAY; ++w) {
2648                         Locale.LOCALE_WEEKDAYS.push(Locale.LOCALE_WEEKDAYS.shift());
2649                     }
2650                 }
2651                 break;
2652         }
2653     },
2654
2655     /**
2656      * The default handler for the "navigator" property
2657      * @method configNavigator
2658      */
2659     configNavigator : function(type, args, obj) {
2660         var val = args[0];
2661         if (YAHOO.widget.CalendarNavigator && (val === true || Lang.isObject(val))) {
2662             if (!this.oNavigator) {
2663                 this.oNavigator = new YAHOO.widget.CalendarNavigator(this);
2664                 // Cleanup DOM Refs/Events before innerHTML is removed.
2665                 this.beforeRenderEvent.subscribe(function () {
2666                     if (!this.pages) {
2667                         this.oNavigator.erase();
2668                     }
2669                 }, this, true);
2670             }
2671         } else {
2672             if (this.oNavigator) {
2673                 this.oNavigator.destroy();
2674                 this.oNavigator = null;
2675             }
2676         }
2677     },
2678
2679     /**
2680     * Defines the style constants for the Calendar
2681     * @method initStyles
2682     */
2683     initStyles : function() {
2684
2685         var defStyle = Calendar.STYLES;
2686
2687         this.Style = {
2688             /**
2689             * @property Style.CSS_ROW_HEADER
2690             */
2691             CSS_ROW_HEADER: defStyle.CSS_ROW_HEADER,
2692             /**
2693             * @property Style.CSS_ROW_FOOTER
2694             */
2695             CSS_ROW_FOOTER: defStyle.CSS_ROW_FOOTER,
2696             /**
2697             * @property Style.CSS_CELL
2698             */
2699             CSS_CELL : defStyle.CSS_CELL,
2700             /**
2701             * @property Style.CSS_CELL_SELECTOR
2702             */
2703             CSS_CELL_SELECTOR : defStyle.CSS_CELL_SELECTOR,
2704             /**
2705             * @property Style.CSS_CELL_SELECTED
2706             */
2707             CSS_CELL_SELECTED : defStyle.CSS_CELL_SELECTED,
2708             /**
2709             * @property Style.CSS_CELL_SELECTABLE
2710             */
2711             CSS_CELL_SELECTABLE : defStyle.CSS_CELL_SELECTABLE,
2712             /**
2713             * @property Style.CSS_CELL_RESTRICTED
2714             */
2715             CSS_CELL_RESTRICTED : defStyle.CSS_CELL_RESTRICTED,
2716             /**
2717             * @property Style.CSS_CELL_TODAY
2718             */
2719             CSS_CELL_TODAY : defStyle.CSS_CELL_TODAY,
2720             /**
2721             * @property Style.CSS_CELL_OOM
2722             */
2723             CSS_CELL_OOM : defStyle.CSS_CELL_OOM,
2724             /**
2725             * @property Style.CSS_CELL_OOB
2726             */
2727             CSS_CELL_OOB : defStyle.CSS_CELL_OOB,
2728             /**
2729             * @property Style.CSS_HEADER
2730             */
2731             CSS_HEADER : defStyle.CSS_HEADER,
2732             /**
2733             * @property Style.CSS_HEADER_TEXT
2734             */
2735             CSS_HEADER_TEXT : defStyle.CSS_HEADER_TEXT,
2736             /**
2737             * @property Style.CSS_BODY
2738             */
2739             CSS_BODY : defStyle.CSS_BODY,
2740             /**
2741             * @property Style.CSS_WEEKDAY_CELL
2742             */
2743             CSS_WEEKDAY_CELL : defStyle.CSS_WEEKDAY_CELL,
2744             /**
2745             * @property Style.CSS_WEEKDAY_ROW
2746             */
2747             CSS_WEEKDAY_ROW : defStyle.CSS_WEEKDAY_ROW,
2748             /**
2749             * @property Style.CSS_FOOTER
2750             */
2751             CSS_FOOTER : defStyle.CSS_FOOTER,
2752             /**
2753             * @property Style.CSS_CALENDAR
2754             */
2755             CSS_CALENDAR : defStyle.CSS_CALENDAR,
2756             /**
2757             * @property Style.CSS_SINGLE
2758             */
2759             CSS_SINGLE : defStyle.CSS_SINGLE,
2760             /**
2761             * @property Style.CSS_CONTAINER
2762             */
2763             CSS_CONTAINER : defStyle.CSS_CONTAINER,
2764             /**
2765             * @property Style.CSS_NAV_LEFT
2766             */
2767             CSS_NAV_LEFT : defStyle.CSS_NAV_LEFT,
2768             /**
2769             * @property Style.CSS_NAV_RIGHT
2770             */
2771             CSS_NAV_RIGHT : defStyle.CSS_NAV_RIGHT,
2772             /**
2773             * @property Style.CSS_NAV
2774             */
2775             CSS_NAV : defStyle.CSS_NAV,
2776             /**
2777             * @property Style.CSS_CLOSE
2778             */
2779             CSS_CLOSE : defStyle.CSS_CLOSE,
2780             /**
2781             * @property Style.CSS_CELL_TOP
2782             */
2783             CSS_CELL_TOP : defStyle.CSS_CELL_TOP,
2784             /**
2785             * @property Style.CSS_CELL_LEFT
2786             */
2787             CSS_CELL_LEFT : defStyle.CSS_CELL_LEFT,
2788             /**
2789             * @property Style.CSS_CELL_RIGHT
2790             */
2791             CSS_CELL_RIGHT : defStyle.CSS_CELL_RIGHT,
2792             /**
2793             * @property Style.CSS_CELL_BOTTOM
2794             */
2795             CSS_CELL_BOTTOM : defStyle.CSS_CELL_BOTTOM,
2796             /**
2797             * @property Style.CSS_CELL_HOVER
2798             */
2799             CSS_CELL_HOVER : defStyle.CSS_CELL_HOVER,
2800             /**
2801             * @property Style.CSS_CELL_HIGHLIGHT1
2802             */
2803             CSS_CELL_HIGHLIGHT1 : defStyle.CSS_CELL_HIGHLIGHT1,
2804             /**
2805             * @property Style.CSS_CELL_HIGHLIGHT2
2806             */
2807             CSS_CELL_HIGHLIGHT2 : defStyle.CSS_CELL_HIGHLIGHT2,
2808             /**
2809             * @property Style.CSS_CELL_HIGHLIGHT3
2810             */
2811             CSS_CELL_HIGHLIGHT3 : defStyle.CSS_CELL_HIGHLIGHT3,
2812             /**
2813             * @property Style.CSS_CELL_HIGHLIGHT4
2814             */
2815             CSS_CELL_HIGHLIGHT4 : defStyle.CSS_CELL_HIGHLIGHT4,
2816             /**
2817              * @property Style.CSS_WITH_TITLE
2818              */
2819             CSS_WITH_TITLE : defStyle.CSS_WITH_TITLE,
2820              /**
2821              * @property Style.CSS_FIXED_SIZE
2822              */
2823             CSS_FIXED_SIZE : defStyle.CSS_FIXED_SIZE,
2824              /**
2825              * @property Style.CSS_LINK_CLOSE
2826              */
2827             CSS_LINK_CLOSE : defStyle.CSS_LINK_CLOSE
2828         };
2829     },
2830
2831     /**
2832     * Builds the date label that will be displayed in the calendar header or
2833     * footer, depending on configuration.
2834     * @method buildMonthLabel
2835     * @return {String} The formatted calendar month label
2836     */
2837     buildMonthLabel : function() {
2838         return this._buildMonthLabel(this.cfg.getProperty(DEF_CFG.PAGEDATE.key));
2839     },
2840
2841     /**
2842      * Helper method, to format a Month Year string, given a JavaScript Date, based on the 
2843      * Calendar localization settings
2844      * 
2845      * @method _buildMonthLabel
2846      * @private
2847      * @param {Date} date
2848      * @return {String} Formated month, year string
2849      */
2850     _buildMonthLabel : function(date) {
2851         var monthLabel  = this.Locale.LOCALE_MONTHS[date.getMonth()] + this.Locale.MY_LABEL_MONTH_SUFFIX,
2852             yearLabel = (date.getFullYear() + this.Locale.YEAR_OFFSET) + this.Locale.MY_LABEL_YEAR_SUFFIX;
2853
2854         if (this.Locale.MY_LABEL_MONTH_POSITION == 2 || this.Locale.MY_LABEL_YEAR_POSITION == 1) {
2855             return yearLabel + monthLabel;
2856         } else {
2857             return monthLabel + yearLabel;
2858         }
2859     },
2860     
2861     /**
2862     * Builds the date digit that will be displayed in calendar cells
2863     * @method buildDayLabel
2864     * @param {Date} workingDate The current working date
2865     * @return {String} The formatted day label
2866     */
2867     buildDayLabel : function(workingDate) {
2868         return workingDate.getDate();
2869     },
2870     
2871     /**
2872      * Creates the title bar element and adds it to Calendar container DIV
2873      * 
2874      * @method createTitleBar
2875      * @param {String} strTitle The title to display in the title bar
2876      * @return The title bar element
2877      */
2878     createTitleBar : function(strTitle) {
2879         var tDiv = Dom.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE, "div", this.oDomContainer)[0] || document.createElement("div");
2880         tDiv.className = YAHOO.widget.CalendarGroup.CSS_2UPTITLE;
2881         tDiv.innerHTML = strTitle;
2882         this.oDomContainer.insertBefore(tDiv, this.oDomContainer.firstChild);
2883     
2884         Dom.addClass(this.oDomContainer, this.Style.CSS_WITH_TITLE);
2885     
2886         return tDiv;
2887     },
2888     
2889     /**
2890      * Removes the title bar element from the DOM
2891      * 
2892      * @method removeTitleBar
2893      */
2894     removeTitleBar : function() {
2895         var tDiv = Dom.getElementsByClassName(YAHOO.widget.CalendarGroup.CSS_2UPTITLE, "div", this.oDomContainer)[0] || null;
2896         if (tDiv) {
2897             Event.purgeElement(tDiv);
2898             this.oDomContainer.removeChild(tDiv);
2899         }
2900         Dom.removeClass(this.oDomContainer, this.Style.CSS_WITH_TITLE);
2901     },
2902     
2903     /**
2904      * Creates the close button HTML element and adds it to Calendar container DIV
2905      * 
2906      * @method createCloseButton
2907      * @return The close HTML element created
2908      */
2909     createCloseButton : function() {
2910         var cssClose = YAHOO.widget.CalendarGroup.CSS_2UPCLOSE,
2911             cssLinkClose = this.Style.CSS_LINK_CLOSE,
2912             DEPR_CLOSE_PATH = "us/my/bn/x_d.gif",
2913             
2914             lnk = Dom.getElementsByClassName(cssLinkClose, "a", this.oDomContainer)[0],
2915             strings = this.cfg.getProperty(DEF_CFG.STRINGS.key),
2916             closeStr = (strings && strings.close) ? strings.close : "";
2917
2918         if (!lnk) {
2919             lnk = document.createElement("a");
2920             Event.addListener(lnk, "click", function(e, cal) {
2921                 cal.hide(); 
2922                 Event.preventDefault(e);
2923             }, this);
2924         }
2925
2926         lnk.href = "#";
2927         lnk.className = cssLinkClose;
2928
2929         if (Calendar.IMG_ROOT !== null) {
2930             var img = Dom.getElementsByClassName(cssClose, "img", lnk)[0] || document.createElement("img");
2931             img.src = Calendar.IMG_ROOT + DEPR_CLOSE_PATH;
2932             img.className = cssClose;
2933             lnk.appendChild(img);
2934         } else {
2935             lnk.innerHTML = '<span class="' + cssClose + ' ' + this.Style.CSS_CLOSE + '">' + closeStr + '</span>';
2936         }
2937         this.oDomContainer.appendChild(lnk);
2938
2939         return lnk;
2940     },
2941     
2942     /**
2943      * Removes the close button HTML element from the DOM
2944      * 
2945      * @method removeCloseButton
2946      */
2947     removeCloseButton : function() {
2948         var btn = Dom.getElementsByClassName(this.Style.CSS_LINK_CLOSE, "a", this.oDomContainer)[0] || null;
2949         if (btn) {
2950             Event.purgeElement(btn);
2951             this.oDomContainer.removeChild(btn);
2952         }
2953     },
2954
2955     /**
2956     * Renders the calendar header.
2957     * @method renderHeader
2958     * @param {Array} html The current working HTML array
2959     * @return {Array} The current working HTML array
2960     */
2961     renderHeader : function(html) {
2962
2963         this.logger.log("Rendering header", "render");
2964
2965         var colSpan = 7,
2966             DEPR_NAV_LEFT = "us/tr/callt.gif",
2967             DEPR_NAV_RIGHT = "us/tr/calrt.gif",
2968             cfg = this.cfg,
2969             pageDate = cfg.getProperty(DEF_CFG.PAGEDATE.key),
2970             strings= cfg.getProperty(DEF_CFG.STRINGS.key),
2971             prevStr = (strings && strings.previousMonth) ?  strings.previousMonth : "",
2972             nextStr = (strings && strings.nextMonth) ? strings.nextMonth : "",
2973             monthLabel;
2974
2975         if (cfg.getProperty(DEF_CFG.SHOW_WEEK_HEADER.key)) {
2976             colSpan += 1;
2977         }
2978     
2979         if (cfg.getProperty(DEF_CFG.SHOW_WEEK_FOOTER.key)) {
2980             colSpan += 1;
2981         }
2982
2983         html[html.length] = "<thead>";
2984         html[html.length] =  "<tr>";
2985         html[html.length] =   '<th colspan="' + colSpan + '" class="' + this.Style.CSS_HEADER_TEXT + '">';
2986         html[html.length] =    '<div class="' + this.Style.CSS_HEADER + '">';
2987
2988         var renderLeft, renderRight = false;
2989
2990         if (this.parent) {
2991             if (this.index === 0) {
2992                 renderLeft = true;
2993             }
2994             if (this.index == (this.parent.cfg.getProperty("pages") -1)) {
2995                 renderRight = true;
2996             }
2997         } else {
2998             renderLeft = true;
2999             renderRight = true;
3000         }
3001
3002         if (renderLeft) {
3003             monthLabel  = this._buildMonthLabel(DateMath.subtract(pageDate, DateMath.MONTH, 1));
3004
3005             var leftArrow = cfg.getProperty(DEF_CFG.NAV_ARROW_LEFT.key);
3006             // 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
3007             if (leftArrow === null && Calendar.IMG_ROOT !== null) {
3008                 leftArrow = Calendar.IMG_ROOT + DEPR_NAV_LEFT;
3009             }
3010             var leftStyle = (leftArrow === null) ? "" : ' style="background-image:url(' + leftArrow + ')"';
3011             html[html.length] = '<a class="' + this.Style.CSS_NAV_LEFT + '"' + leftStyle + ' href="#">' + prevStr + ' (' + monthLabel + ')' + '</a>';
3012         }
3013
3014         var lbl = this.buildMonthLabel();
3015         var cal = this.parent || this;
3016         if (cal.cfg.getProperty("navigator")) {
3017             lbl = "<a class=\"" + this.Style.CSS_NAV + "\" href=\"#\">" + lbl + "</a>";
3018         }
3019         html[html.length] = lbl;
3020
3021         if (renderRight) {
3022             monthLabel  = this._buildMonthLabel(DateMath.add(pageDate, DateMath.MONTH, 1));
3023
3024             var rightArrow = cfg.getProperty(DEF_CFG.NAV_ARROW_RIGHT.key);
3025             if (rightArrow === null && Calendar.IMG_ROOT !== null) {
3026                 rightArrow = Calendar.IMG_ROOT + DEPR_NAV_RIGHT;
3027             }
3028             var rightStyle = (rightArrow === null) ? "" : ' style="background-image:url(' + rightArrow + ')"';
3029             html[html.length] = '<a class="' + this.Style.CSS_NAV_RIGHT + '"' + rightStyle + ' href="#">' + nextStr + ' (' + monthLabel + ')' + '</a>';
3030         }
3031
3032         html[html.length] = '</div>\n</th>\n</tr>';
3033
3034         if (cfg.getProperty(DEF_CFG.SHOW_WEEKDAYS.key)) {
3035             html = this.buildWeekdays(html);
3036         }
3037         
3038         html[html.length] = '</thead>';
3039     
3040         return html;
3041     },
3042     
3043     /**
3044     * Renders the Calendar's weekday headers.
3045     * @method buildWeekdays
3046     * @param {Array} html The current working HTML array
3047     * @return {Array} The current working HTML array
3048     */
3049     buildWeekdays : function(html) {
3050
3051         html[html.length] = '<tr class="' + this.Style.CSS_WEEKDAY_ROW + '">';
3052
3053         if (this.cfg.getProperty(DEF_CFG.SHOW_WEEK_HEADER.key)) {
3054             html[html.length] = '<th>&#160;</th>';
3055         }
3056
3057         for(var i=0;i < this.Locale.LOCALE_WEEKDAYS.length; ++i) {
3058             html[html.length] = '<th class="' + this.Style.CSS_WEEKDAY_CELL + '">' + this.Locale.LOCALE_WEEKDAYS[i] + '</th>';
3059         }
3060
3061         if (this.cfg.getProperty(DEF_CFG.SHOW_WEEK_FOOTER.key)) {
3062             html[html.length] = '<th>&#160;</th>';
3063         }
3064
3065         html[html.length] = '</tr>';
3066
3067         return html;
3068     },
3069     
3070     /**
3071     * Renders the calendar body.
3072     * @method renderBody
3073     * @param {Date} workingDate The current working Date being used for the render process
3074     * @param {Array} html The current working HTML array
3075     * @return {Array} The current working HTML array
3076     */
3077     renderBody : function(workingDate, html) {
3078         this.logger.log("Rendering body", "render");
3079
3080         var startDay = this.cfg.getProperty(DEF_CFG.START_WEEKDAY.key);
3081
3082         this.preMonthDays = workingDate.getDay();
3083         if (startDay > 0) {
3084             this.preMonthDays -= startDay;
3085         }
3086         if (this.preMonthDays < 0) {
3087             this.preMonthDays += 7;
3088         }
3089
3090         this.monthDays = DateMath.findMonthEnd(workingDate).getDate();
3091         this.postMonthDays = Calendar.DISPLAY_DAYS-this.preMonthDays-this.monthDays;
3092
3093         this.logger.log(this.preMonthDays + " preciding out-of-month days", "render");
3094         this.logger.log(this.monthDays + " month days", "render");
3095         this.logger.log(this.postMonthDays + " post-month days", "render");
3096
3097         workingDate = DateMath.subtract(workingDate, DateMath.DAY, this.preMonthDays);
3098         this.logger.log("Calendar page starts on " + workingDate, "render");
3099     
3100         var weekNum,
3101             weekClass,
3102             weekPrefix = "w",
3103             cellPrefix = "_cell",
3104             workingDayPrefix = "wd",
3105             dayPrefix = "d",
3106             cellRenderers,
3107             renderer,
3108             t = this.today,
3109             cfg = this.cfg,
3110             todayYear = t.getFullYear(),
3111             todayMonth = t.getMonth(),
3112             todayDate = t.getDate(),
3113             useDate = cfg.getProperty(DEF_CFG.PAGEDATE.key),
3114             hideBlankWeeks = cfg.getProperty(DEF_CFG.HIDE_BLANK_WEEKS.key),
3115             showWeekFooter = cfg.getProperty(DEF_CFG.SHOW_WEEK_FOOTER.key),
3116             showWeekHeader = cfg.getProperty(DEF_CFG.SHOW_WEEK_HEADER.key),
3117             mindate = cfg.getProperty(DEF_CFG.MINDATE.key),
3118             maxdate = cfg.getProperty(DEF_CFG.MAXDATE.key),
3119             yearOffset = this.Locale.YEAR_OFFSET;
3120
3121         if (mindate) {
3122             mindate = DateMath.clearTime(mindate);
3123         }
3124         if (maxdate) {
3125             maxdate = DateMath.clearTime(maxdate);
3126         }
3127
3128         html[html.length] = '<tbody class="m' + (useDate.getMonth()+1) + ' ' + this.Style.CSS_BODY + '">';
3129
3130         var i = 0,
3131             tempDiv = document.createElement("div"),
3132             cell = document.createElement("td");
3133
3134         tempDiv.appendChild(cell);
3135
3136         var cal = this.parent || this;
3137
3138         for (var r=0;r<6;r++) {
3139             weekNum = DateMath.getWeekNumber(workingDate, startDay);
3140             weekClass = weekPrefix + weekNum;
3141
3142             // Local OOM check for performance, since we already have pagedate
3143             if (r !== 0 && hideBlankWeeks === true && workingDate.getMonth() != useDate.getMonth()) {
3144                 break;
3145             } else {
3146                 html[html.length] = '<tr class="' + weekClass + '">';
3147
3148                 if (showWeekHeader) { html = this.renderRowHeader(weekNum, html); }
3149
3150                 for (var d=0; d < 7; d++){ // Render actual days
3151
3152                     cellRenderers = [];
3153
3154                     this.clearElement(cell);
3155                     cell.className = this.Style.CSS_CELL;
3156                     cell.id = this.id + cellPrefix + i;
3157                     this.logger.log("Rendering cell " + cell.id + " (" + workingDate.getFullYear() + yearOffset + "-" + (workingDate.getMonth()+1) + "-" + workingDate.getDate() + ")", "cellrender");
3158
3159                     if (workingDate.getDate()  == todayDate && 
3160                         workingDate.getMonth()  == todayMonth &&
3161                         workingDate.getFullYear() == todayYear) {
3162                         cellRenderers[cellRenderers.length]=cal.renderCellStyleToday;
3163                     }
3164
3165                     var workingArray = [workingDate.getFullYear(),workingDate.getMonth()+1,workingDate.getDate()];
3166                     this.cellDates[this.cellDates.length] = workingArray; // Add this date to cellDates
3167
3168                     // Local OOM check for performance, since we already have pagedate
3169                     if (workingDate.getMonth() != useDate.getMonth()) {
3170                         cellRenderers[cellRenderers.length]=cal.renderCellNotThisMonth;
3171                     } else {
3172                         Dom.addClass(cell, workingDayPrefix + workingDate.getDay());
3173                         Dom.addClass(cell, dayPrefix + workingDate.getDate());
3174
3175                         for (var s=0;s<this.renderStack.length;++s) {
3176
3177                             renderer = null;
3178
3179                             var rArray = this.renderStack[s],
3180                                 type = rArray[0],
3181                                 month,
3182                                 day,
3183                                 year;
3184
3185                             switch (type) {
3186                                 case Calendar.DATE:
3187                                     month = rArray[1][1];
3188                                     day = rArray[1][2];
3189                                     year = rArray[1][0];
3190
3191                                     if (workingDate.getMonth()+1 == month && workingDate.getDate() == day && workingDate.getFullYear() == year) {
3192                                         renderer = rArray[2];
3193                                         this.renderStack.splice(s,1);
3194                                     }
3195                                     break;
3196                                 case Calendar.MONTH_DAY:
3197                                     month = rArray[1][0];
3198                                     day = rArray[1][1];
3199
3200                                     if (workingDate.getMonth()+1 == month && workingDate.getDate() == day) {
3201                                         renderer = rArray[2];
3202                                         this.renderStack.splice(s,1);
3203                                     }
3204                                     break;
3205                                 case Calendar.RANGE:
3206                                     var date1 = rArray[1][0],
3207                                         date2 = rArray[1][1],
3208                                         d1month = date1[1],
3209                                         d1day = date1[2],
3210                                         d1year = date1[0],
3211                                         d1 = DateMath.getDate(d1year, d1month-1, d1day),
3212                                         d2month = date2[1],
3213                                         d2day = date2[2],
3214                                         d2year = date2[0],
3215                                         d2 = DateMath.getDate(d2year, d2month-1, d2day);
3216
3217                                     if (workingDate.getTime() >= d1.getTime() && workingDate.getTime() <= d2.getTime()) {
3218                                         renderer = rArray[2];
3219
3220                                         if (workingDate.getTime()==d2.getTime()) { 
3221                                             this.renderStack.splice(s,1);
3222                                         }
3223                                     }
3224                                     break;
3225                                 case Calendar.WEEKDAY:
3226                                     var weekday = rArray[1][0];
3227                                     if (workingDate.getDay()+1 == weekday) {
3228                                         renderer = rArray[2];
3229                                     }
3230                                     break;
3231                                 case Calendar.MONTH:
3232                                     month = rArray[1][0];
3233                                     if (workingDate.getMonth()+1 == month) {
3234                                         renderer = rArray[2];
3235                                     }
3236                                     break;
3237                             }
3238
3239                             if (renderer) {
3240                                 cellRenderers[cellRenderers.length]=renderer;
3241                             }
3242                         }
3243
3244                     }
3245
3246                     if (this._indexOfSelectedFieldArray(workingArray) > -1) {
3247                         cellRenderers[cellRenderers.length]=cal.renderCellStyleSelected; 
3248                     }
3249
3250                     if ((mindate && (workingDate.getTime() < mindate.getTime())) ||
3251                         (maxdate && (workingDate.getTime() > maxdate.getTime()))
3252                     ) {
3253                         cellRenderers[cellRenderers.length]=cal.renderOutOfBoundsDate;
3254                     } else {
3255                         cellRenderers[cellRenderers.length]=cal.styleCellDefault;
3256                         cellRenderers[cellRenderers.length]=cal.renderCellDefault; 
3257                     }
3258
3259                     for (var x=0; x < cellRenderers.length; ++x) {
3260                         this.logger.log("renderer[" + x + "] for (" + workingDate.getFullYear() + yearOffset + "-" + (workingDate.getMonth()+1) + "-" + workingDate.getDate() + ")", "cellrender");
3261                         if (cellRenderers[x].call(cal, workingDate, cell) == Calendar.STOP_RENDER) {
3262                             break;
3263                         }
3264                     }
3265
3266                     workingDate.setTime(workingDate.getTime() + DateMath.ONE_DAY_MS);
3267                     // Just in case we crossed DST/Summertime boundaries
3268                     workingDate = DateMath.clearTime(workingDate);
3269
3270                     if (i >= 0 && i <= 6) {
3271                         Dom.addClass(cell, this.Style.CSS_CELL_TOP);
3272                     }
3273                     if ((i % 7) === 0) {
3274                         Dom.addClass(cell, this.Style.CSS_CELL_LEFT);
3275                     }
3276                     if (((i+1) % 7) === 0) {
3277                         Dom.addClass(cell, this.Style.CSS_CELL_RIGHT);
3278                     }
3279
3280                     var postDays = this.postMonthDays; 
3281                     if (hideBlankWeeks && postDays >= 7) {
3282                         var blankWeeks = Math.floor(postDays/7);
3283                         for (var p=0;p<blankWeeks;++p) {
3284                             postDays -= 7;
3285                         }
3286                     }
3287                     
3288                     if (i >= ((this.preMonthDays+postDays+this.monthDays)-7)) {
3289                         Dom.addClass(cell, this.Style.CSS_CELL_BOTTOM);
3290                     }
3291     
3292                     html[html.length] = tempDiv.innerHTML;
3293                     i++;
3294                 }
3295     
3296                 if (showWeekFooter) { html = this.renderRowFooter(weekNum, html); }
3297     
3298                 html[html.length] = '</tr>';
3299             }
3300         }
3301     
3302         html[html.length] = '</tbody>';
3303     
3304         return html;
3305     },
3306     
3307     /**
3308     * Renders the calendar footer. In the default implementation, there is
3309     * no footer.
3310     * @method renderFooter
3311     * @param {Array} html The current working HTML array
3312     * @return {Array} The current working HTML array
3313     */
3314     renderFooter : function(html) { return html; },
3315     
3316     /**
3317     * Renders the calendar after it has been configured. The render() method has a specific call chain that will execute
3318     * when the method is called: renderHeader, renderBody, renderFooter.
3319     * Refer to the documentation for those methods for information on 
3320     * individual render tasks.
3321     * @method render
3322     */
3323     render : function() {
3324         this.beforeRenderEvent.fire();
3325
3326         // Find starting day of the current month
3327         var workingDate = DateMath.findMonthStart(this.cfg.getProperty(DEF_CFG.PAGEDATE.key));
3328
3329         this.resetRenderers();
3330         this.cellDates.length = 0;
3331
3332         Event.purgeElement(this.oDomContainer, true);
3333
3334         var html = [];
3335
3336         html[html.length] = '<table cellSpacing="0" class="' + this.Style.CSS_CALENDAR + ' y' + (workingDate.getFullYear() + this.Locale.YEAR_OFFSET) +'" id="' + this.id + '">';
3337         html = this.renderHeader(html);
3338         html = this.renderBody(workingDate, html);
3339         html = this.renderFooter(html);
3340         html[html.length] = '</table>';
3341
3342         this.oDomContainer.innerHTML = html.join("\n");
3343
3344         this.applyListeners();
3345         this.cells = Dom.getElementsByClassName(this.Style.CSS_CELL, "td", this.id);
3346     
3347         this.cfg.refireEvent(DEF_CFG.TITLE.key);
3348         this.cfg.refireEvent(DEF_CFG.CLOSE.key);
3349         this.cfg.refireEvent(DEF_CFG.IFRAME.key);
3350
3351         this.renderEvent.fire();
3352     },
3353
3354     /**
3355     * Applies the Calendar's DOM listeners to applicable elements.
3356     * @method applyListeners
3357     */
3358     applyListeners : function() {
3359         var root = this.oDomContainer,
3360             cal = this.parent || this,
3361             anchor = "a",
3362             click = "click";
3363
3364         var linkLeft = Dom.getElementsByClassName(this.Style.CSS_NAV_LEFT, anchor, root),
3365             linkRight = Dom.getElementsByClassName(this.Style.CSS_NAV_RIGHT, anchor, root);
3366
3367         if (linkLeft && linkLeft.length > 0) {
3368             this.linkLeft = linkLeft[0];
3369             Event.addListener(this.linkLeft, click, this.doPreviousMonthNav, cal, true);
3370         }
3371
3372         if (linkRight && linkRight.length > 0) {
3373             this.linkRight = linkRight[0];
3374             Event.addListener(this.linkRight, click, this.doNextMonthNav, cal, true);
3375         }
3376
3377         if (cal.cfg.getProperty("navigator") !== null) {
3378             this.applyNavListeners();
3379         }
3380
3381         if (this.domEventMap) {
3382             var el,elements;
3383             for (var cls in this.domEventMap) { 
3384                 if (Lang.hasOwnProperty(this.domEventMap, cls)) {
3385                     var items = this.domEventMap[cls];
3386     
3387                     if (! (items instanceof Array)) {
3388                         items = [items];
3389                     }
3390     
3391                     for (var i=0;i<items.length;i++) {
3392                         var item = items[i];
3393                         elements = Dom.getElementsByClassName(cls, item.tag, this.oDomContainer);
3394     
3395                         for (var c=0;c<elements.length;c++) {
3396                             el = elements[c];
3397                              Event.addListener(el, item.event, item.handler, item.scope, item.correct );
3398                         }
3399                     }
3400                 }
3401             }
3402         }
3403
3404         Event.addListener(this.oDomContainer, "click", this.doSelectCell, this);
3405         Event.addListener(this.oDomContainer, "mouseover", this.doCellMouseOver, this);
3406         Event.addListener(this.oDomContainer, "mouseout", this.doCellMouseOut, this);
3407     },
3408
3409     applyNavListeners : function() {
3410         var calParent = this.parent || this,
3411             cal = this,
3412             navBtns = Dom.getElementsByClassName(this.Style.CSS_NAV, "a", this.oDomContainer);
3413
3414         if (navBtns.length > 0) {
3415
3416             Event.addListener(navBtns, "click", function (e, obj) {
3417                 var target = Event.getTarget(e);
3418                 // this == navBtn
3419                 if (this === target || Dom.isAncestor(this, target)) {
3420                     Event.preventDefault(e);
3421                 }
3422                 var navigator = calParent.oNavigator;
3423                 if (navigator) {
3424                     var pgdate = cal.cfg.getProperty("pagedate");
3425                     navigator.setYear(pgdate.getFullYear() + cal.Locale.YEAR_OFFSET);
3426                     navigator.setMonth(pgdate.getMonth());
3427                     navigator.show();
3428                 }
3429             });
3430         }
3431     },
3432
3433     /**
3434     * Retrieves the Date object for the specified Calendar cell
3435     * @method getDateByCellId
3436     * @param {String} id The id of the cell
3437     * @return {Date} The Date object for the specified Calendar cell
3438     */
3439     getDateByCellId : function(id) {
3440         var date = this.getDateFieldsByCellId(id);
3441         return (date) ? DateMath.getDate(date[0],date[1]-1,date[2]) : null;
3442     },
3443     
3444     /**
3445     * Retrieves the Date object for the specified Calendar cell
3446     * @method getDateFieldsByCellId
3447     * @param {String} id The id of the cell
3448     * @return {Array} The array of Date fields for the specified Calendar cell
3449     */
3450     getDateFieldsByCellId : function(id) {
3451         id = this.getIndexFromId(id);
3452         return (id > -1) ? this.cellDates[id] : null;
3453     },
3454
3455     /**
3456      * Find the Calendar's cell index for a given date.
3457      * If the date is not found, the method returns -1.
3458      * <p>
3459      * The returned index can be used to lookup the cell HTMLElement  
3460      * using the Calendar's cells array or passed to selectCell to select 
3461      * cells by index. 
3462      * </p>
3463      *
3464      * See <a href="#cells">cells</a>, <a href="#selectCell">selectCell</a>.
3465      *
3466      * @method getCellIndex
3467      * @param {Date} date JavaScript Date object, for which to find a cell index.
3468      * @return {Number} The index of the date in Calendars cellDates/cells arrays, or -1 if the date 
3469      * is not on the curently rendered Calendar page.
3470      */
3471     getCellIndex : function(date) {
3472         var idx = -1;
3473         if (date) {
3474             var m = date.getMonth(),
3475                 y = date.getFullYear(),
3476                 d = date.getDate(),
3477                 dates = this.cellDates;
3478
3479             for (var i = 0; i < dates.length; ++i) {
3480                 var cellDate = dates[i];
3481                 if (cellDate[0] === y && cellDate[1] === m+1 && cellDate[2] === d) {
3482                     idx = i;
3483                     break;
3484                 }
3485             }
3486         }
3487         return idx;
3488     },
3489
3490     /**
3491      * Given the id used to mark each Calendar cell, this method
3492      * extracts the index number from the id.
3493      * 
3494      * @param {String} strId The cell id
3495      * @return {Number} The index of the cell, or -1 if id does not contain an index number
3496      */
3497     getIndexFromId : function(strId) {
3498         var idx = -1,
3499             li = strId.lastIndexOf("_cell");
3500
3501         if (li > -1) {
3502             idx = parseInt(strId.substring(li + 5), 10);
3503         }
3504
3505         return idx;
3506     },
3507     
3508     // BEGIN BUILT-IN TABLE CELL RENDERERS
3509     
3510     /**
3511     * Renders a cell that falls before the minimum date or after the maximum date.
3512     * widget class.
3513     * @method renderOutOfBoundsDate
3514     * @param {Date}     workingDate  The current working Date object being used to generate the calendar
3515     * @param {HTMLTableCellElement} cell   The current working cell in the calendar
3516     * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
3517     *   should not be terminated
3518     */
3519     renderOutOfBoundsDate : function(workingDate, cell) {
3520         Dom.addClass(cell, this.Style.CSS_CELL_OOB);
3521         cell.innerHTML = workingDate.getDate();
3522         return Calendar.STOP_RENDER;
3523     },
3524
3525     /**
3526     * Renders the row header for a week.
3527     * @method renderRowHeader
3528     * @param {Number} weekNum The week number of the current row
3529     * @param {Array} cell The current working HTML array
3530     */
3531     renderRowHeader : function(weekNum, html) {
3532         html[html.length] = '<th class="' + this.Style.CSS_ROW_HEADER + '">' + weekNum + '</th>';
3533         return html;
3534     },
3535
3536     /**
3537     * Renders the row footer for a week.
3538     * @method renderRowFooter
3539     * @param {Number} weekNum The week number of the current row
3540     * @param {Array} cell The current working HTML array
3541     */
3542     renderRowFooter : function(weekNum, html) {
3543         html[html.length] = '<th class="' + this.Style.CSS_ROW_FOOTER + '">' + weekNum + '</th>';
3544         return html;
3545     },
3546     
3547     /**
3548     * Renders a single standard calendar cell in the calendar widget table.
3549     * All logic for determining how a standard default cell will be rendered is 
3550     * encapsulated in this method, and must be accounted for when extending the
3551     * widget class.
3552     * @method renderCellDefault
3553     * @param {Date}     workingDate  The current working Date object being used to generate the calendar
3554     * @param {HTMLTableCellElement} cell   The current working cell in the calendar
3555     */
3556     renderCellDefault : function(workingDate, cell) {
3557         cell.innerHTML = '<a href="#" class="' + this.Style.CSS_CELL_SELECTOR + '">' + this.buildDayLabel(workingDate) + "</a>";
3558     },
3559     
3560     /**
3561     * Styles a selectable cell.
3562     * @method styleCellDefault
3563     * @param {Date}     workingDate  The current working Date object being used to generate the calendar
3564     * @param {HTMLTableCellElement} cell   The current working cell in the calendar
3565     */
3566     styleCellDefault : function(workingDate, cell) {
3567         Dom.addClass(cell, this.Style.CSS_CELL_SELECTABLE);
3568     },
3569     
3570     
3571     /**
3572     * Renders a single standard calendar cell using the CSS hightlight1 style
3573     * @method renderCellStyleHighlight1
3574     * @param {Date}     workingDate  The current working Date object being used to generate the calendar
3575     * @param {HTMLTableCellElement} cell   The current working cell in the calendar
3576     */
3577     renderCellStyleHighlight1 : function(workingDate, cell) {
3578         Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT1);
3579     },
3580     
3581     /**
3582     * Renders a single standard calendar cell using the CSS hightlight2 style
3583     * @method renderCellStyleHighlight2
3584     * @param {Date}     workingDate  The current working Date object being used to generate the calendar
3585     * @param {HTMLTableCellElement} cell   The current working cell in the calendar
3586     */
3587     renderCellStyleHighlight2 : function(workingDate, cell) {
3588         Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT2);
3589     },
3590     
3591     /**
3592     * Renders a single standard calendar cell using the CSS hightlight3 style
3593     * @method renderCellStyleHighlight3
3594     * @param {Date}     workingDate  The current working Date object being used to generate the calendar
3595     * @param {HTMLTableCellElement} cell   The current working cell in the calendar
3596     */
3597     renderCellStyleHighlight3 : function(workingDate, cell) {
3598         Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT3);
3599     },
3600     
3601     /**
3602     * Renders a single standard calendar cell using the CSS hightlight4 style
3603     * @method renderCellStyleHighlight4
3604     * @param {Date}     workingDate  The current working Date object being used to generate the calendar
3605     * @param {HTMLTableCellElement} cell   The current working cell in the calendar
3606     */
3607     renderCellStyleHighlight4 : function(workingDate, cell) {
3608         Dom.addClass(cell, this.Style.CSS_CELL_HIGHLIGHT4);
3609     },
3610     
3611     /**
3612     * Applies the default style used for rendering today's date to the current calendar cell
3613     * @method renderCellStyleToday
3614     * @param {Date}     workingDate  The current working Date object being used to generate the calendar
3615     * @param {HTMLTableCellElement} cell   The current working cell in the calendar
3616     */
3617     renderCellStyleToday : function(workingDate, cell) {
3618         Dom.addClass(cell, this.Style.CSS_CELL_TODAY);
3619     },
3620
3621     /**
3622     * Applies the default style used for rendering selected dates to the current calendar cell
3623     * @method renderCellStyleSelected
3624     * @param {Date}     workingDate  The current working Date object being used to generate the calendar
3625     * @param {HTMLTableCellElement} cell   The current working cell in the calendar
3626     * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
3627     *   should not be terminated
3628     */
3629     renderCellStyleSelected : function(workingDate, cell) {
3630         Dom.addClass(cell, this.Style.CSS_CELL_SELECTED);
3631     },
3632     
3633     /**
3634     * Applies the default style used for rendering dates that are not a part of the current
3635     * month (preceding or trailing the cells for the current month)
3636     * @method renderCellNotThisMonth
3637     * @param {Date}     workingDate  The current working Date object being used to generate the calendar
3638     * @param {HTMLTableCellElement} cell   The current working cell in the calendar
3639     * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
3640     *   should not be terminated
3641     */
3642     renderCellNotThisMonth : function(workingDate, cell) {
3643         Dom.addClass(cell, this.Style.CSS_CELL_OOM);
3644         cell.innerHTML=workingDate.getDate();
3645         return Calendar.STOP_RENDER;
3646     },
3647     
3648     /**
3649     * Renders the current calendar cell as a non-selectable "black-out" date using the default
3650     * restricted style.
3651     * @method renderBodyCellRestricted
3652     * @param {Date}     workingDate  The current working Date object being used to generate the calendar
3653     * @param {HTMLTableCellElement} cell   The current working cell in the calendar
3654     * @return {String} YAHOO.widget.Calendar.STOP_RENDER if rendering should stop with this style, null or nothing if rendering
3655     *   should not be terminated
3656     */
3657     renderBodyCellRestricted : function(workingDate, cell) {
3658         Dom.addClass(cell, this.Style.CSS_CELL);
3659         Dom.addClass(cell, this.Style.CSS_CELL_RESTRICTED);
3660         cell.innerHTML=workingDate.getDate();
3661         return Calendar.STOP_RENDER;
3662     },
3663     
3664     // END BUILT-IN TABLE CELL RENDERERS
3665     
3666     // BEGIN MONTH NAVIGATION METHODS
3667
3668     /**
3669     * Adds the designated number of months to the current calendar month, and sets the current
3670     * calendar page date to the new month.
3671     * @method addMonths
3672     * @param {Number} count The number of months to add to the current calendar
3673     */
3674     addMonths : function(count) {
3675         var cfgPageDate = DEF_CFG.PAGEDATE.key,
3676
3677         prevDate = this.cfg.getProperty(cfgPageDate),
3678         newDate = DateMath.add(prevDate, DateMath.MONTH, count);
3679
3680         this.cfg.setProperty(cfgPageDate, newDate);
3681         this.resetRenderers();
3682         this.changePageEvent.fire(prevDate, newDate);
3683     },
3684
3685     /**
3686     * Subtracts the designated number of months from the current calendar month, and sets the current
3687     * calendar page date to the new month.
3688     * @method subtractMonths
3689     * @param {Number} count The number of months to subtract from the current calendar
3690     */
3691     subtractMonths : function(count) {
3692         this.addMonths(-1*count);
3693     },
3694
3695     /**
3696     * Adds the designated number of years to the current calendar, and sets the current
3697     * calendar page date to the new month.
3698     * @method addYears
3699     * @param {Number} count The number of years to add to the current calendar
3700     */
3701     addYears : function(count) {
3702         var cfgPageDate = DEF_CFG.PAGEDATE.key,
3703
3704         prevDate = this.cfg.getProperty(cfgPageDate),
3705         newDate = DateMath.add(prevDate, DateMath.YEAR, count);
3706
3707         this.cfg.setProperty(cfgPageDate, newDate);
3708         this.resetRenderers();
3709         this.changePageEvent.fire(prevDate, newDate);
3710     },
3711
3712     /**
3713     * Subtcats the designated number of years from the current calendar, and sets the current
3714     * calendar page date to the new month.
3715     * @method subtractYears
3716     * @param {Number} count The number of years to subtract from the current calendar
3717     */
3718     subtractYears : function(count) {
3719         this.addYears(-1*count);
3720     },
3721
3722     /**
3723     * Navigates to the next month page in the calendar widget.
3724     * @method nextMonth
3725     */
3726     nextMonth : function() {
3727         this.addMonths(1);
3728     },
3729     
3730     /**
3731     * Navigates to the previous month page in the calendar widget.
3732     * @method previousMonth
3733     */
3734     previousMonth : function() {
3735         this.addMonths(-1);
3736     },
3737     
3738     /**
3739     * Navigates to the next year in the currently selected month in the calendar widget.
3740     * @method nextYear
3741     */
3742     nextYear : function() {
3743         this.addYears(1);
3744     },
3745     
3746     /**
3747     * Navigates to the previous year in the currently selected month in the calendar widget.
3748     * @method previousYear
3749     */
3750     previousYear : function() {
3751         this.addYears(-1);
3752     },
3753
3754     // END MONTH NAVIGATION METHODS
3755     
3756     // BEGIN SELECTION METHODS
3757     
3758     /**
3759     * Resets the calendar widget to the originally selected month and year, and 
3760     * sets the calendar to the initial selection(s).
3761     * @method reset
3762     */
3763     reset : function() {
3764         this.cfg.resetProperty(DEF_CFG.SELECTED.key);
3765         this.cfg.resetProperty(DEF_CFG.PAGEDATE.key);
3766         this.resetEvent.fire();
3767     },
3768     
3769     /**
3770     * Clears the selected dates in the current calendar widget and sets the calendar
3771     * to the current month and year.
3772     * @method clear
3773     */
3774     clear : function() {
3775         this.cfg.setProperty(DEF_CFG.SELECTED.key, []);
3776         this.cfg.setProperty(DEF_CFG.PAGEDATE.key, new Date(this.today.getTime()));
3777         this.clearEvent.fire();
3778     },
3779     
3780     /**
3781     * Selects a date or a collection of dates on the current calendar. This method, by default,
3782     * does not call the render method explicitly. Once selection has completed, render must be 
3783     * called for the changes to be reflected visually.
3784     *
3785     * Any dates which are OOB (out of bounds, not selectable) will not be selected and the array of 
3786     * selected dates passed to the selectEvent will not contain OOB dates.
3787     * 
3788     * If all dates are OOB, the no state change will occur; beforeSelect and select events will not be fired.
3789     *
3790     * @method select
3791     * @param {String/Date/Date[]} date The date string of dates to select in the current calendar. Valid formats are
3792     *        individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
3793     *        Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
3794     *        This method can also take a JavaScript Date object or an array of Date objects.
3795     * @return {Date[]}   Array of JavaScript Date objects representing all individual dates that are currently selected.
3796     */
3797     select : function(date) {
3798         this.logger.log("Select: " + date, "info");
3799
3800         var aToBeSelected = this._toFieldArray(date),
3801             validDates = [],
3802             selected = [],
3803             cfgSelected = DEF_CFG.SELECTED.key;
3804
3805         this.logger.log("Selection field array: " + aToBeSelected, "info");
3806         
3807         for (var a=0; a < aToBeSelected.length; ++a) {
3808             var toSelect = aToBeSelected[a];
3809
3810             if (!this.isDateOOB(this._toDate(toSelect))) {
3811
3812                 if (validDates.length === 0) {
3813                     this.beforeSelectEvent.fire();
3814                     selected = this.cfg.getProperty(cfgSelected);
3815                 }
3816                 validDates.push(toSelect);
3817
3818                 if (this._indexOfSelectedFieldArray(toSelect) == -1) { 
3819                     selected[selected.length] = toSelect;
3820                 }
3821             }
3822         }
3823
3824         if (validDates.length === 0) { this.logger.log("All provided dates were OOB. beforeSelect and select events not fired", "info"); }
3825
3826         if (validDates.length > 0) {
3827             if (this.parent) {
3828                 this.parent.cfg.setProperty(cfgSelected, selected);
3829             } else {
3830                 this.cfg.setProperty(cfgSelected, selected);
3831             }
3832             this.selectEvent.fire(validDates);
3833         }
3834
3835         return this.getSelectedDates();
3836     },
3837     
3838     /**
3839     * Selects a date on the current calendar by referencing the index of the cell that should be selected.
3840     * This method is used to easily select a single cell (usually with a mouse click) without having to do
3841     * a full render. The selected style is applied to the cell directly.
3842     *
3843     * If the cell is not marked with the CSS_CELL_SELECTABLE class (as is the case by default for out of month 
3844     * or out of bounds cells), it will not be selected and in such a case beforeSelect and select events will not be fired.
3845     * 
3846     * @method selectCell
3847     * @param {Number} cellIndex The index of the cell to select in the current calendar. 
3848     * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
3849     */
3850     selectCell : function(cellIndex) {
3851
3852         var cell = this.cells[cellIndex],
3853             cellDate = this.cellDates[cellIndex],
3854             dCellDate = this._toDate(cellDate),
3855             selectable = Dom.hasClass(cell, this.Style.CSS_CELL_SELECTABLE);
3856
3857         this.logger.log("Select: " + dCellDate, "info");
3858         if (!selectable) {this.logger.log("The cell at cellIndex:" + cellIndex + " is not a selectable cell. beforeSelect, select events not fired", "info"); }
3859
3860         if (selectable) {
3861     
3862             this.beforeSelectEvent.fire();
3863     
3864             var cfgSelected = DEF_CFG.SELECTED.key;
3865             var selected = this.cfg.getProperty(cfgSelected);
3866     
3867             var selectDate = cellDate.concat();
3868     
3869             if (this._indexOfSelectedFieldArray(selectDate) == -1) {
3870                 selected[selected.length] = selectDate;
3871             }
3872             if (this.parent) {
3873                 this.parent.cfg.setProperty(cfgSelected, selected);
3874             } else {
3875                 this.cfg.setProperty(cfgSelected, selected);
3876             }
3877             this.renderCellStyleSelected(dCellDate,cell);
3878             this.selectEvent.fire([selectDate]);
3879     
3880             this.doCellMouseOut.call(cell, null, this);  
3881         }
3882     
3883         return this.getSelectedDates();
3884     },
3885     
3886     /**
3887     * Deselects a date or a collection of dates on the current calendar. This method, by default,
3888     * does not call the render method explicitly. Once deselection has completed, render must be 
3889     * called for the changes to be reflected visually.
3890     * 
3891     * The method will not attempt to deselect any dates which are OOB (out of bounds, and hence not selectable) 
3892     * and the array of deselected dates passed to the deselectEvent will not contain any OOB dates.
3893     * 
3894     * If all dates are OOB, beforeDeselect and deselect events will not be fired.
3895     * 
3896     * @method deselect
3897     * @param {String/Date/Date[]} date The date string of dates to deselect in the current calendar. Valid formats are
3898     *        individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
3899     *        Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
3900     *        This method can also take a JavaScript Date object or an array of Date objects. 
3901     * @return {Date[]}   Array of JavaScript Date objects representing all individual dates that are currently selected.
3902     */
3903     deselect : function(date) {
3904         this.logger.log("Deselect: " + date, "info");
3905
3906         var aToBeDeselected = this._toFieldArray(date),
3907             validDates = [],
3908             selected = [],
3909             cfgSelected = DEF_CFG.SELECTED.key;
3910
3911         this.logger.log("Deselection field array: " + aToBeDeselected, "info");
3912
3913         for (var a=0; a < aToBeDeselected.length; ++a) {
3914             var toDeselect = aToBeDeselected[a];
3915     
3916             if (!this.isDateOOB(this._toDate(toDeselect))) {
3917     
3918                 if (validDates.length === 0) {
3919                     this.beforeDeselectEvent.fire();
3920                     selected = this.cfg.getProperty(cfgSelected);
3921                 }
3922     
3923                 validDates.push(toDeselect);
3924     
3925                 var index = this._indexOfSelectedFieldArray(toDeselect);
3926                 if (index != -1) { 
3927                     selected.splice(index,1);
3928                 }
3929             }
3930         }
3931     
3932         if (validDates.length === 0) { this.logger.log("All provided dates were OOB. beforeDeselect and deselect events not fired");}
3933     
3934         if (validDates.length > 0) {
3935             if (this.parent) {
3936                 this.parent.cfg.setProperty(cfgSelected, selected);
3937             } else {
3938                 this.cfg.setProperty(cfgSelected, selected);
3939             }
3940             this.deselectEvent.fire(validDates);
3941         }
3942     
3943         return this.getSelectedDates();
3944     },
3945     
3946     /**
3947     * Deselects a date on the current calendar by referencing the index of the cell that should be deselected.
3948     * This method is used to easily deselect a single cell (usually with a mouse click) without having to do
3949     * a full render. The selected style is removed from the cell directly.
3950     * 
3951     * If the cell is not marked with the CSS_CELL_SELECTABLE class (as is the case by default for out of month 
3952     * or out of bounds cells), the method will not attempt to deselect it and in such a case, beforeDeselect and 
3953     * deselect events will not be fired.
3954     * 
3955     * @method deselectCell
3956     * @param {Number} cellIndex The index of the cell to deselect in the current calendar. 
3957     * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
3958     */
3959     deselectCell : function(cellIndex) {
3960         var cell = this.cells[cellIndex],
3961             cellDate = this.cellDates[cellIndex],
3962             cellDateIndex = this._indexOfSelectedFieldArray(cellDate);
3963
3964         var selectable = Dom.hasClass(cell, this.Style.CSS_CELL_SELECTABLE);
3965         if (!selectable) { this.logger.log("The cell at cellIndex:" + cellIndex + " is not a selectable/deselectable cell", "info"); }
3966
3967         if (selectable) {
3968
3969             this.beforeDeselectEvent.fire();
3970
3971             var selected = this.cfg.getProperty(DEF_CFG.SELECTED.key),
3972                 dCellDate = this._toDate(cellDate),
3973                 selectDate = cellDate.concat();
3974
3975             if (cellDateIndex > -1) {
3976                 if (this.cfg.getProperty(DEF_CFG.PAGEDATE.key).getMonth() == dCellDate.getMonth() &&
3977                     this.cfg.getProperty(DEF_CFG.PAGEDATE.key).getFullYear() == dCellDate.getFullYear()) {
3978                     Dom.removeClass(cell, this.Style.CSS_CELL_SELECTED);
3979                 }
3980                 selected.splice(cellDateIndex, 1);
3981             }
3982
3983             if (this.parent) {
3984                 this.parent.cfg.setProperty(DEF_CFG.SELECTED.key, selected);
3985             } else {
3986                 this.cfg.setProperty(DEF_CFG.SELECTED.key, selected);
3987             }
3988
3989             this.deselectEvent.fire([selectDate]);
3990         }
3991
3992         return this.getSelectedDates();
3993     },
3994
3995     /**
3996     * Deselects all dates on the current calendar.
3997     * @method deselectAll
3998     * @return {Date[]}  Array of JavaScript Date objects representing all individual dates that are currently selected.
3999     *      Assuming that this function executes properly, the return value should be an empty array.
4000     *      However, the empty array is returned for the sake of being able to check the selection status
4001     *      of the calendar.
4002     */
4003     deselectAll : function() {
4004         this.beforeDeselectEvent.fire();
4005         
4006         var cfgSelected = DEF_CFG.SELECTED.key,
4007             selected = this.cfg.getProperty(cfgSelected),
4008             count = selected.length,
4009             sel = selected.concat();
4010
4011         if (this.parent) {
4012             this.parent.cfg.setProperty(cfgSelected, []);
4013         } else {
4014             this.cfg.setProperty(cfgSelected, []);
4015         }
4016         
4017         if (count > 0) {
4018             this.deselectEvent.fire(sel);
4019         }
4020     
4021         return this.getSelectedDates();
4022     },
4023     
4024     // END SELECTION METHODS
4025     
4026     // BEGIN TYPE CONVERSION METHODS
4027     
4028     /**
4029     * Converts a date (either a JavaScript Date object, or a date string) to the internal data structure
4030     * used to represent dates: [[yyyy,mm,dd],[yyyy,mm,dd]].
4031     * @method _toFieldArray
4032     * @private
4033     * @param {String/Date/Date[]} date The date string of dates to deselect in the current calendar. Valid formats are
4034     *        individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
4035     *        Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
4036     *        This method can also take a JavaScript Date object or an array of Date objects. 
4037     * @return {Array[](Number[])} Array of date field arrays
4038     */
4039     _toFieldArray : function(date) {
4040         var returnDate = [];
4041     
4042         if (date instanceof Date) {
4043             returnDate = [[date.getFullYear(), date.getMonth()+1, date.getDate()]];
4044         } else if (Lang.isString(date)) {
4045             returnDate = this._parseDates(date);
4046         } else if (Lang.isArray(date)) {
4047             for (var i=0;i<date.length;++i) {
4048                 var d = date[i];
4049                 returnDate[returnDate.length] = [d.getFullYear(),d.getMonth()+1,d.getDate()];
4050             }
4051         }
4052         
4053         return returnDate;
4054     },
4055     
4056     /**
4057     * Converts a date field array [yyyy,mm,dd] to a JavaScript Date object. The date field array
4058     * is the format in which dates are as provided as arguments to selectEvent and deselectEvent listeners.
4059     * 
4060     * @method toDate
4061     * @param {Number[]} dateFieldArray The date field array to convert to a JavaScript Date.
4062     * @return {Date} JavaScript Date object representing the date field array.
4063     */
4064     toDate : function(dateFieldArray) {
4065         return this._toDate(dateFieldArray);
4066     },
4067     
4068     /**
4069     * Converts a date field array [yyyy,mm,dd] to a JavaScript Date object.
4070     * @method _toDate
4071     * @private
4072     * @deprecated Made public, toDate 
4073     * @param {Number[]}  dateFieldArray The date field array to convert to a JavaScript Date.
4074     * @return {Date} JavaScript Date object representing the date field array
4075     */
4076     _toDate : function(dateFieldArray) {
4077         if (dateFieldArray instanceof Date) {
4078             return dateFieldArray;
4079         } else {
4080             return DateMath.getDate(dateFieldArray[0],dateFieldArray[1]-1,dateFieldArray[2]);
4081         }
4082     },
4083     
4084     // END TYPE CONVERSION METHODS 
4085     
4086     // BEGIN UTILITY METHODS
4087     
4088     /**
4089     * Determines if 2 field arrays are equal.
4090     * @method _fieldArraysAreEqual
4091     * @private
4092     * @param {Number[]} array1 The first date field array to compare
4093     * @param {Number[]} array2 The first date field array to compare
4094     * @return {Boolean} The boolean that represents the equality of the two arrays
4095     */
4096     _fieldArraysAreEqual : function(array1, array2) {
4097         var match = false;
4098     
4099         if (array1[0]==array2[0]&&array1[1]==array2[1]&&array1[2]==array2[2]) {
4100             match=true; 
4101         }
4102     
4103         return match;
4104     },
4105     
4106     /**
4107     * Gets the index of a date field array [yyyy,mm,dd] in the current list of selected dates.
4108     * @method _indexOfSelectedFieldArray
4109     * @private
4110     * @param {Number[]}  find The date field array to search for
4111     * @return {Number}   The index of the date field array within the collection of selected dates.
4112     *        -1 will be returned if the date is not found.
4113     */
4114     _indexOfSelectedFieldArray : function(find) {
4115         var selected = -1,
4116             seldates = this.cfg.getProperty(DEF_CFG.SELECTED.key);
4117     
4118         for (var s=0;s<seldates.length;++s) {
4119             var sArray = seldates[s];
4120             if (find[0]==sArray[0]&&find[1]==sArray[1]&&find[2]==sArray[2]) {
4121                 selected = s;
4122                 break;
4123             }
4124         }
4125     
4126         return selected;
4127     },
4128     
4129     /**
4130     * Determines whether a given date is OOM (out of month).
4131     * @method isDateOOM
4132     * @param {Date} date The JavaScript Date object for which to check the OOM status
4133     * @return {Boolean} true if the date is OOM
4134     */
4135     isDateOOM : function(date) {
4136         return (date.getMonth() != this.cfg.getProperty(DEF_CFG.PAGEDATE.key).getMonth());
4137     },
4138     
4139     /**
4140     * Determines whether a given date is OOB (out of bounds - less than the mindate or more than the maxdate).
4141     *
4142     * @method isDateOOB
4143     * @param {Date} date The JavaScript Date object for which to check the OOB status
4144     * @return {Boolean} true if the date is OOB
4145     */
4146     isDateOOB : function(date) {
4147         var minDate = this.cfg.getProperty(DEF_CFG.MINDATE.key),
4148             maxDate = this.cfg.getProperty(DEF_CFG.MAXDATE.key),
4149             dm = DateMath;
4150         
4151         if (minDate) {
4152             minDate = dm.clearTime(minDate);
4153         } 
4154         if (maxDate) {
4155             maxDate = dm.clearTime(maxDate);
4156         }
4157     
4158         var clearedDate = new Date(date.getTime());
4159         clearedDate = dm.clearTime(clearedDate);
4160     
4161         return ((minDate && clearedDate.getTime() < minDate.getTime()) || (maxDate && clearedDate.getTime() > maxDate.getTime()));
4162     },
4163     
4164     /**
4165      * Parses a pagedate configuration property value. The value can either be specified as a string of form "mm/yyyy" or a Date object 
4166      * 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 
4167      * @method _parsePageDate
4168      * @private
4169      * @param {Date|String} date Pagedate value which needs to be parsed
4170      * @return {Date} The Date object representing the pagedate
4171      */
4172     _parsePageDate : function(date) {
4173         var parsedDate;
4174
4175         if (date) {
4176             if (date instanceof Date) {
4177                 parsedDate = DateMath.findMonthStart(date);
4178             } else {
4179                 var month, year, aMonthYear;
4180                 aMonthYear = date.split(this.cfg.getProperty(DEF_CFG.DATE_FIELD_DELIMITER.key));
4181                 month = parseInt(aMonthYear[this.cfg.getProperty(DEF_CFG.MY_MONTH_POSITION.key)-1], 10)-1;
4182                 year = parseInt(aMonthYear[this.cfg.getProperty(DEF_CFG.MY_YEAR_POSITION.key)-1], 10) - this.Locale.YEAR_OFFSET;
4183
4184                 parsedDate = DateMath.getDate(year, month, 1);
4185             }
4186         } else {
4187             parsedDate = DateMath.getDate(this.today.getFullYear(), this.today.getMonth(), 1);
4188         }
4189         return parsedDate;
4190     },
4191     
4192     // END UTILITY METHODS
4193     
4194     // BEGIN EVENT HANDLERS
4195     
4196     /**
4197     * Event executed before a date is selected in the calendar widget.
4198     * @deprecated Event handlers for this event should be susbcribed to beforeSelectEvent.
4199     */
4200     onBeforeSelect : function() {
4201         if (this.cfg.getProperty(DEF_CFG.MULTI_SELECT.key) === false) {
4202             if (this.parent) {
4203                 this.parent.callChildFunction("clearAllBodyCellStyles", this.Style.CSS_CELL_SELECTED);
4204                 this.parent.deselectAll();
4205             } else {
4206                 this.clearAllBodyCellStyles(this.Style.CSS_CELL_SELECTED);
4207                 this.deselectAll();
4208             }
4209         }
4210     },
4211     
4212     /**
4213     * Event executed when a date is selected in the calendar widget.
4214     * @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] ]
4215     * @deprecated Event handlers for this event should be susbcribed to selectEvent.
4216     */
4217     onSelect : function(selected) { },
4218     
4219     /**
4220     * Event executed before a date is deselected in the calendar widget.
4221     * @deprecated Event handlers for this event should be susbcribed to beforeDeselectEvent.
4222     */
4223     onBeforeDeselect : function() { },
4224     
4225     /**
4226     * Event executed when a date is deselected in the calendar widget.
4227     * @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] ]
4228     * @deprecated Event handlers for this event should be susbcribed to deselectEvent.
4229     */
4230     onDeselect : function(deselected) { },
4231     
4232     /**
4233     * Event executed when the user navigates to a different calendar page.
4234     * @deprecated Event handlers for this event should be susbcribed to changePageEvent.
4235     */
4236     onChangePage : function() {
4237         this.render();
4238     },
4239
4240     /**
4241     * Event executed when the calendar widget is rendered.
4242     * @deprecated Event handlers for this event should be susbcribed to renderEvent.
4243     */
4244     onRender : function() { },
4245
4246     /**
4247     * Event executed when the calendar widget is reset to its original state.
4248     * @deprecated Event handlers for this event should be susbcribed to resetEvemt.
4249     */
4250     onReset : function() { this.render(); },
4251
4252     /**
4253     * Event executed when the calendar widget is completely cleared to the current month with no selections.
4254     * @deprecated Event handlers for this event should be susbcribed to clearEvent.
4255     */
4256     onClear : function() { this.render(); },
4257     
4258     /**
4259     * Validates the calendar widget. This method has no default implementation
4260     * and must be extended by subclassing the widget.
4261     * @return Should return true if the widget validates, and false if
4262     * it doesn't.
4263     * @type Boolean
4264     */
4265     validate : function() { return true; },
4266     
4267     // END EVENT HANDLERS
4268     
4269     // BEGIN DATE PARSE METHODS
4270     
4271     /**
4272     * Converts a date string to a date field array
4273     * @private
4274     * @param {String} sDate   Date string. Valid formats are mm/dd and mm/dd/yyyy.
4275     * @return    A date field array representing the string passed to the method
4276     * @type Array[](Number[])
4277     */
4278     _parseDate : function(sDate) {
4279         var aDate = sDate.split(this.Locale.DATE_FIELD_DELIMITER),
4280             rArray;
4281
4282         if (aDate.length == 2) {
4283             rArray = [aDate[this.Locale.MD_MONTH_POSITION-1],aDate[this.Locale.MD_DAY_POSITION-1]];
4284             rArray.type = Calendar.MONTH_DAY;
4285         } else {
4286             rArray = [aDate[this.Locale.MDY_YEAR_POSITION-1] - this.Locale.YEAR_OFFSET, aDate[this.Locale.MDY_MONTH_POSITION-1],aDate[this.Locale.MDY_DAY_POSITION-1]];
4287             rArray.type = Calendar.DATE;
4288         }
4289
4290         for (var i=0;i<rArray.length;i++) {
4291             rArray[i] = parseInt(rArray[i], 10);
4292         }
4293     
4294         return rArray;
4295     },
4296     
4297     /**
4298     * Converts a multi or single-date string to an array of date field arrays
4299     * @private
4300     * @param {String} sDates  Date string with one or more comma-delimited dates. Valid formats are mm/dd, mm/dd/yyyy, mm/dd/yyyy-mm/dd/yyyy
4301     * @return       An array of date field arrays
4302     * @type Array[](Number[])
4303     */
4304     _parseDates : function(sDates) {
4305         var aReturn = [],
4306             aDates = sDates.split(this.Locale.DATE_DELIMITER);
4307         
4308         for (var d=0;d<aDates.length;++d) {
4309             var sDate = aDates[d];
4310     
4311             if (sDate.indexOf(this.Locale.DATE_RANGE_DELIMITER) != -1) {
4312                 // This is a range
4313                 var aRange = sDate.split(this.Locale.DATE_RANGE_DELIMITER),
4314                     dateStart = this._parseDate(aRange[0]),
4315                     dateEnd = this._parseDate(aRange[1]),
4316                     fullRange = this._parseRange(dateStart, dateEnd);
4317
4318                 aReturn = aReturn.concat(fullRange);
4319             } else {
4320                 // This is not a range
4321                 var aDate = this._parseDate(sDate);
4322                 aReturn.push(aDate);
4323             }
4324         }
4325         return aReturn;
4326     },
4327     
4328     /**
4329     * Converts a date range to the full list of included dates
4330     * @private
4331     * @param {Number[]} startDate Date field array representing the first date in the range
4332     * @param {Number[]} endDate  Date field array representing the last date in the range
4333     * @return       An array of date field arrays
4334     * @type Array[](Number[])
4335     */
4336     _parseRange : function(startDate, endDate) {
4337         var dCurrent = DateMath.add(DateMath.getDate(startDate[0],startDate[1]-1,startDate[2]),DateMath.DAY,1),
4338             dEnd     = DateMath.getDate(endDate[0],  endDate[1]-1,  endDate[2]),
4339             results = [];
4340
4341         results.push(startDate);
4342         while (dCurrent.getTime() <= dEnd.getTime()) {
4343             results.push([dCurrent.getFullYear(),dCurrent.getMonth()+1,dCurrent.getDate()]);
4344             dCurrent = DateMath.add(dCurrent,DateMath.DAY,1);
4345         }
4346         return results;
4347     },
4348     
4349     // END DATE PARSE METHODS
4350     
4351     // BEGIN RENDERER METHODS
4352     
4353     /**
4354     * Resets the render stack of the current calendar to its original pre-render value.
4355     */
4356     resetRenderers : function() {
4357         this.renderStack = this._renderStack.concat();
4358     },
4359     
4360     /**
4361      * Removes all custom renderers added to the Calendar through the addRenderer, addMonthRenderer and 
4362      * addWeekdayRenderer methods. Calendar's render method needs to be called after removing renderers 
4363      * to re-render the Calendar without custom renderers applied.
4364      */
4365     removeRenderers : function() {
4366         this._renderStack = [];
4367         this.renderStack = [];
4368     },
4369
4370     /**
4371     * Clears the inner HTML, CSS class and style information from the specified cell.
4372     * @method clearElement
4373     * @param {HTMLTableCellElement} cell The cell to clear
4374     */ 
4375     clearElement : function(cell) {
4376         cell.innerHTML = "&#160;";
4377         cell.className="";
4378     },
4379     
4380     /**
4381     * Adds a renderer to the render stack. The function reference passed to this method will be executed
4382     * when a date cell matches the conditions specified in the date string for this renderer.
4383     * @method addRenderer
4384     * @param {String} sDates  A date string to associate with the specified renderer. Valid formats
4385     *         include date (12/24/2005), month/day (12/24), and range (12/1/2004-1/1/2005)
4386     * @param {Function} fnRender The function executed to render cells that match the render rules for this renderer.
4387     */
4388     addRenderer : function(sDates, fnRender) {
4389         var aDates = this._parseDates(sDates);
4390         for (var i=0;i<aDates.length;++i) {
4391             var aDate = aDates[i];
4392         
4393             if (aDate.length == 2) { // this is either a range or a month/day combo
4394                 if (aDate[0] instanceof Array) { // this is a range
4395                     this._addRenderer(Calendar.RANGE,aDate,fnRender);
4396                 } else { // this is a month/day combo
4397                     this._addRenderer(Calendar.MONTH_DAY,aDate,fnRender);
4398                 }
4399             } else if (aDate.length == 3) {
4400                 this._addRenderer(Calendar.DATE,aDate,fnRender);
4401             }
4402         }
4403     },
4404     
4405     /**
4406     * The private method used for adding cell renderers to the local render stack.
4407     * This method is called by other methods that set the renderer type prior to the method call.
4408     * @method _addRenderer
4409     * @private
4410     * @param {String} type  The type string that indicates the type of date renderer being added.
4411     *         Values are YAHOO.widget.Calendar.DATE, YAHOO.widget.Calendar.MONTH_DAY, YAHOO.widget.Calendar.WEEKDAY,
4412     *         YAHOO.widget.Calendar.RANGE, YAHOO.widget.Calendar.MONTH
4413     * @param {Array}  aDates  An array of dates used to construct the renderer. The format varies based
4414     *         on the renderer type
4415     * @param {Function} fnRender The function executed to render cells that match the render rules for this renderer.
4416     */
4417     _addRenderer : function(type, aDates, fnRender) {
4418         var add = [type,aDates,fnRender];
4419         this.renderStack.unshift(add); 
4420         this._renderStack = this.renderStack.concat();
4421     },
4422
4423     /**
4424     * Adds a month to the render stack. The function reference passed to this method will be executed
4425     * when a date cell matches the month passed to this method.
4426     * @method addMonthRenderer
4427     * @param {Number} month  The month (1-12) to associate with this renderer
4428     * @param {Function} fnRender The function executed to render cells that match the render rules for this renderer.
4429     */
4430     addMonthRenderer : function(month, fnRender) {
4431         this._addRenderer(Calendar.MONTH,[month],fnRender);
4432     },
4433
4434     /**
4435     * Adds a weekday to the render stack. The function reference passed to this method will be executed
4436     * when a date cell matches the weekday passed to this method.
4437     * @method addWeekdayRenderer
4438     * @param {Number} weekday  The weekday (Sunday = 1, Monday = 2 ... Saturday = 7) to associate with this renderer
4439     * @param {Function} fnRender The function executed to render cells that match the render rules for this renderer.
4440     */
4441     addWeekdayRenderer : function(weekday, fnRender) {
4442         this._addRenderer(Calendar.WEEKDAY,[weekday],fnRender);
4443     },
4444
4445     // END RENDERER METHODS
4446     
4447     // BEGIN CSS METHODS
4448     
4449     /**
4450     * Removes all styles from all body cells in the current calendar table.
4451     * @method clearAllBodyCellStyles
4452     * @param {style} style The CSS class name to remove from all calendar body cells
4453     */
4454     clearAllBodyCellStyles : function(style) {
4455         for (var c=0;c<this.cells.length;++c) {
4456             Dom.removeClass(this.cells[c],style);
4457         }
4458     },
4459     
4460     // END CSS METHODS
4461     
4462     // BEGIN GETTER/SETTER METHODS
4463     /**
4464     * Sets the calendar's month explicitly
4465     * @method setMonth
4466     * @param {Number} month  The numeric month, from 0 (January) to 11 (December)
4467     */
4468     setMonth : function(month) {
4469         var cfgPageDate = DEF_CFG.PAGEDATE.key,
4470             current = this.cfg.getProperty(cfgPageDate);
4471         current.setMonth(parseInt(month, 10));
4472         this.cfg.setProperty(cfgPageDate, current);
4473     },
4474
4475     /**
4476     * Sets the calendar's year explicitly.
4477     * @method setYear
4478     * @param {Number} year  The numeric 4-digit year
4479     */
4480     setYear : function(year) {
4481         var cfgPageDate = DEF_CFG.PAGEDATE.key,
4482             current = this.cfg.getProperty(cfgPageDate);
4483
4484         current.setFullYear(parseInt(year, 10) - this.Locale.YEAR_OFFSET);
4485         this.cfg.setProperty(cfgPageDate, current);
4486     },
4487
4488     /**
4489     * Gets the list of currently selected dates from the calendar.
4490     * @method getSelectedDates
4491     * @return {Date[]} An array of currently selected JavaScript Date objects.
4492     */
4493     getSelectedDates : function() {
4494         var returnDates = [],
4495             selected = this.cfg.getProperty(DEF_CFG.SELECTED.key);
4496
4497         for (var d=0;d<selected.length;++d) {
4498             var dateArray = selected[d];
4499
4500             var date = DateMath.getDate(dateArray[0],dateArray[1]-1,dateArray[2]);
4501             returnDates.push(date);
4502         }
4503
4504         returnDates.sort( function(a,b) { return a-b; } );
4505         return returnDates;
4506     },
4507
4508     /// END GETTER/SETTER METHODS ///
4509     
4510     /**
4511     * Hides the Calendar's outer container from view.
4512     * @method hide
4513     */
4514     hide : function() {
4515         if (this.beforeHideEvent.fire()) {
4516             this.oDomContainer.style.display = "none";
4517             this.hideEvent.fire();
4518         }
4519     },
4520
4521     /**
4522     * Shows the Calendar's outer container.
4523     * @method show
4524     */
4525     show : function() {
4526         if (this.beforeShowEvent.fire()) {
4527             this.oDomContainer.style.display = "block";
4528             this.showEvent.fire();
4529         }
4530     },
4531
4532     /**
4533     * Returns a string representing the current browser.
4534     * @deprecated As of 2.3.0, environment information is available in YAHOO.env.ua
4535     * @see YAHOO.env.ua
4536     * @property browser
4537     * @type String
4538     */
4539     browser : (function() {
4540                 var ua = navigator.userAgent.toLowerCase();
4541                       if (ua.indexOf('opera')!=-1) { // Opera (check first in case of spoof)
4542                          return 'opera';
4543                       } else if (ua.indexOf('msie 7')!=-1) { // IE7
4544                          return 'ie7';
4545                       } else if (ua.indexOf('msie') !=-1) { // IE
4546                          return 'ie';
4547                       } else if (ua.indexOf('safari')!=-1) { // Safari (check before Gecko because it includes "like Gecko")
4548                          return 'safari';
4549                       } else if (ua.indexOf('gecko') != -1) { // Gecko
4550                          return 'gecko';
4551                       } else {
4552                          return false;
4553                       }
4554                 })(),
4555     /**
4556     * Returns a string representation of the object.
4557     * @method toString
4558     * @return {String} A string representation of the Calendar object.
4559     */
4560     toString : function() {
4561         return "Calendar " + this.id;
4562     },
4563
4564     /**
4565      * Destroys the Calendar instance. The method will remove references
4566      * to HTML elements, remove any event listeners added by the Calendar,
4567      * and destroy the Config and CalendarNavigator instances it has created.
4568      *
4569      * @method destroy
4570      */
4571     destroy : function() {
4572
4573         if (this.beforeDestroyEvent.fire()) {
4574             var cal = this;
4575
4576             // Child objects
4577             if (cal.navigator) {
4578                 cal.navigator.destroy();
4579             }
4580
4581             if (cal.cfg) {
4582                 cal.cfg.destroy();
4583             }
4584
4585             // DOM event listeners
4586             Event.purgeElement(cal.oDomContainer, true);
4587
4588             // Generated markup/DOM - Not removing the container DIV since we didn't create it.
4589             Dom.removeClass(cal.oDomContainer, cal.Style.CSS_WITH_TITLE);
4590             Dom.removeClass(cal.oDomContainer, cal.Style.CSS_CONTAINER);
4591             Dom.removeClass(cal.oDomContainer, cal.Style.CSS_SINGLE);
4592             cal.oDomContainer.innerHTML = "";
4593
4594             // JS-to-DOM references
4595             cal.oDomContainer = null;
4596             cal.cells = null;
4597
4598             this.destroyEvent.fire();
4599         }
4600     }
4601 };
4602
4603 YAHOO.widget.Calendar = Calendar;
4604
4605 /**
4606 * @namespace YAHOO.widget
4607 * @class Calendar_Core
4608 * @extends YAHOO.widget.Calendar
4609 * @deprecated The old Calendar_Core class is no longer necessary.
4610 */
4611 YAHOO.widget.Calendar_Core = YAHOO.widget.Calendar;
4612
4613 YAHOO.widget.Cal_Core = YAHOO.widget.Calendar;
4614
4615 })();
4616 (function() {
4617
4618     var Dom = YAHOO.util.Dom,
4619         DateMath = YAHOO.widget.DateMath,
4620         Event = YAHOO.util.Event,
4621         Lang = YAHOO.lang,
4622         Calendar = YAHOO.widget.Calendar;
4623
4624 /**
4625 * YAHOO.widget.CalendarGroup is a special container class for YAHOO.widget.Calendar. This class facilitates
4626 * the ability to have multi-page calendar views that share a single dataset and are
4627 * dependent on each other.
4628 *
4629 * The calendar group instance will refer to each of its elements using a 0-based index.
4630 * For example, to construct the placeholder for a calendar group widget with id "cal1" and
4631 * containerId of "cal1Container", the markup would be as follows:
4632 *   <xmp>
4633 *       <div id="cal1Container_0"></div>
4634 *       <div id="cal1Container_1"></div>
4635 *   </xmp>
4636 * The tables for the calendars ("cal1_0" and "cal1_1") will be inserted into those containers.
4637 *
4638 * <p>
4639 * <strong>NOTE: As of 2.4.0, the constructor's ID argument is optional.</strong>
4640 * The CalendarGroup can be constructed by simply providing a container ID string, 
4641 * or a reference to a container DIV HTMLElement (the element needs to exist 
4642 * in the document).
4643
4644 * E.g.:
4645 *   <xmp>
4646 *       var c = new YAHOO.widget.CalendarGroup("calContainer", configOptions);
4647 *   </xmp>
4648 * or:
4649 *   <xmp>
4650 *       var containerDiv = YAHOO.util.Dom.get("calContainer");
4651 *       var c = new YAHOO.widget.CalendarGroup(containerDiv, configOptions);
4652 *   </xmp>
4653 * </p>
4654 * <p>
4655 * If not provided, the ID will be generated from the container DIV ID by adding an "_t" suffix.
4656 * 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".
4657 * </p>
4658
4659 * @namespace YAHOO.widget
4660 * @class CalendarGroup
4661 * @constructor
4662 * @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.
4663 * @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.
4664 * @param {Object} config optional The configuration object containing the initial configuration values for the CalendarGroup.
4665 */
4666 function CalendarGroup(id, containerId, config) {
4667     if (arguments.length > 0) {
4668         this.init.apply(this, arguments);
4669     }
4670 }
4671
4672 /**
4673 * The set of default Config property keys and values for the CalendarGroup.
4674
4675 * <p>
4676 * NOTE: This property is made public in order to allow users to change 
4677 * the default values of configuration properties. Users should not 
4678 * modify the key string, unless they are overriding the Calendar implementation
4679 * </p>
4680 *
4681 * @property YAHOO.widget.CalendarGroup.DEFAULT_CONFIG
4682 * @static
4683 * @type Object An object with key/value pairs, the key being the 
4684 * uppercase configuration property name and the value being an objec 
4685 * literal with a key string property, and a value property, specifying the 
4686 * default value of the property 
4687 */
4688
4689 /**
4690 * The set of default Config property keys and values for the CalendarGroup
4691 * @property YAHOO.widget.CalendarGroup._DEFAULT_CONFIG
4692 * @deprecated Made public. See the public DEFAULT_CONFIG property for details
4693 * @private
4694 * @static
4695 * @type Object
4696 */
4697 CalendarGroup.DEFAULT_CONFIG = CalendarGroup._DEFAULT_CONFIG = Calendar.DEFAULT_CONFIG;
4698 CalendarGroup.DEFAULT_CONFIG.PAGES = {key:"pages", value:2};
4699
4700 var DEF_CFG = CalendarGroup.DEFAULT_CONFIG;
4701
4702 CalendarGroup.prototype = {
4703
4704     /**
4705     * Initializes the calendar group. All subclasses must call this method in order for the
4706     * group to be initialized properly.
4707     * @method init
4708     * @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.
4709     * @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.
4710     * @param {Object} config optional The configuration object containing the initial configuration values for the CalendarGroup.
4711     */
4712     init : function(id, container, config) {
4713
4714         // Normalize 2.4.0, pre 2.4.0 args
4715         var nArgs = this._parseArgs(arguments);
4716
4717         id = nArgs.id;
4718         container = nArgs.container;
4719         config = nArgs.config;
4720
4721         this.oDomContainer = Dom.get(container);
4722         if (!this.oDomContainer) { this.logger.log("Container not found in document.", "error"); }
4723
4724         if (!this.oDomContainer.id) {
4725             this.oDomContainer.id = Dom.generateId();
4726         }
4727         if (!id) {
4728             id = this.oDomContainer.id + "_t";
4729         }
4730
4731         /**
4732         * The unique id associated with the CalendarGroup
4733         * @property id
4734         * @type String
4735         */
4736         this.id = id;
4737
4738         /**
4739         * The unique id associated with the CalendarGroup container
4740         * @property containerId
4741         * @type String
4742         */
4743         this.containerId = this.oDomContainer.id;
4744
4745         this.logger = new YAHOO.widget.LogWriter("CalendarGroup " + this.id);
4746         this.initEvents();
4747         this.initStyles();
4748
4749         /**
4750         * The collection of Calendar pages contained within the CalendarGroup
4751         * @property pages
4752         * @type YAHOO.widget.Calendar[]
4753         */
4754         this.pages = [];
4755
4756         Dom.addClass(this.oDomContainer, CalendarGroup.CSS_CONTAINER);
4757         Dom.addClass(this.oDomContainer, CalendarGroup.CSS_MULTI_UP);
4758
4759         /**
4760         * The Config object used to hold the configuration variables for the CalendarGroup
4761         * @property cfg
4762         * @type YAHOO.util.Config
4763         */
4764         this.cfg = new YAHOO.util.Config(this);
4765
4766         /**
4767         * The local object which contains the CalendarGroup's options
4768         * @property Options
4769         * @type Object
4770         */
4771         this.Options = {};
4772
4773         /**
4774         * The local object which contains the CalendarGroup's locale settings
4775         * @property Locale
4776         * @type Object
4777         */
4778         this.Locale = {};
4779
4780         this.setupConfig();
4781
4782         if (config) {
4783             this.cfg.applyConfig(config, true);
4784         }
4785
4786         this.cfg.fireQueue();
4787
4788         // OPERA HACK FOR MISWRAPPED FLOATS
4789         if (YAHOO.env.ua.opera){
4790             this.renderEvent.subscribe(this._fixWidth, this, true);
4791             this.showEvent.subscribe(this._fixWidth, this, true);
4792         }
4793
4794         this.logger.log("Initialized " + this.pages.length + "-page CalendarGroup", "info");
4795     },
4796
4797     setupConfig : function() {
4798
4799         var cfg = this.cfg;
4800
4801         /**
4802         * The number of pages to include in the CalendarGroup. This value can only be set once, in the CalendarGroup's constructor arguments.
4803         * @config pages
4804         * @type Number
4805         * @default 2
4806         */
4807         cfg.addProperty(DEF_CFG.PAGES.key, { value:DEF_CFG.PAGES.value, validator:cfg.checkNumber, handler:this.configPages } );
4808
4809         /**
4810         * The positive or negative year offset from the Gregorian calendar year (assuming a January 1st rollover) to 
4811         * be used when displaying or parsing dates.  NOTE: All JS Date objects returned by methods, or expected as input by
4812         * methods will always represent the Gregorian year, in order to maintain date/month/week values.
4813         *
4814         * @config year_offset
4815         * @type Number
4816         * @default 0
4817         */
4818         cfg.addProperty(DEF_CFG.YEAR_OFFSET.key, { value:DEF_CFG.YEAR_OFFSET.value, handler: this.delegateConfig, supercedes:DEF_CFG.YEAR_OFFSET.supercedes, suppressEvent:true } );
4819
4820         /**
4821         * The date to use to represent "Today".
4822         *
4823         * @config today
4824         * @type Date
4825         * @default Today's date
4826         */
4827         cfg.addProperty(DEF_CFG.TODAY.key, { value: new Date(DEF_CFG.TODAY.value.getTime()), supercedes:DEF_CFG.TODAY.supercedes, handler: this.configToday, suppressEvent:false } );
4828
4829         /**
4830         * The month/year representing the current visible Calendar date (mm/yyyy)
4831         * @config pagedate
4832         * @type String | Date
4833         * @default Today's date
4834         */
4835         cfg.addProperty(DEF_CFG.PAGEDATE.key, { value: DEF_CFG.PAGEDATE.value || new Date(DEF_CFG.TODAY.value.getTime()), handler:this.configPageDate } );
4836
4837         /**
4838         * The date or range of dates representing the current Calendar selection
4839         *
4840         * @config selected
4841         * @type String
4842         * @default []
4843         */
4844         cfg.addProperty(DEF_CFG.SELECTED.key, { value:[], handler:this.configSelected } );
4845
4846         /**
4847         * The title to display above the CalendarGroup's month header
4848         * @config title
4849         * @type String
4850         * @default ""
4851         */
4852         cfg.addProperty(DEF_CFG.TITLE.key, { value:DEF_CFG.TITLE.value, handler:this.configTitle } );
4853
4854         /**
4855         * Whether or not a close button should be displayed for this CalendarGroup
4856         * @config close
4857         * @type Boolean
4858         * @default false
4859         */
4860         cfg.addProperty(DEF_CFG.CLOSE.key, { value:DEF_CFG.CLOSE.value, handler:this.configClose } );
4861
4862         /**
4863         * 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.
4864         * This property is enabled by default for IE6 and below. It is disabled by default for other browsers for performance reasons, but can be 
4865         * enabled if required.
4866         * 
4867         * @config iframe
4868         * @type Boolean
4869         * @default true for IE6 and below, false for all other browsers
4870         */
4871         cfg.addProperty(DEF_CFG.IFRAME.key, { value:DEF_CFG.IFRAME.value, handler:this.configIframe, validator:cfg.checkBoolean } );
4872
4873         /**
4874         * The minimum selectable date in the current Calendar (mm/dd/yyyy)
4875         * @config mindate
4876         * @type String | Date
4877         * @default null
4878         */
4879         cfg.addProperty(DEF_CFG.MINDATE.key, { value:DEF_CFG.MINDATE.value, handler:this.delegateConfig } );
4880
4881         /**
4882         * The maximum selectable date in the current Calendar (mm/dd/yyyy)
4883         * @config maxdate
4884         * @type String | Date
4885         * @default null
4886         */
4887         cfg.addProperty(DEF_CFG.MAXDATE.key, { value:DEF_CFG.MAXDATE.value, handler:this.delegateConfig  } );
4888
4889         // Options properties
4890
4891         /**
4892         * True if the Calendar should allow multiple selections. False by default.
4893         * @config MULTI_SELECT
4894         * @type Boolean
4895         * @default false
4896         */
4897         cfg.addProperty(DEF_CFG.MULTI_SELECT.key, { value:DEF_CFG.MULTI_SELECT.value, handler:this.delegateConfig, validator:cfg.checkBoolean } );
4898
4899         /**
4900         * The weekday the week begins on. Default is 0 (Sunday).
4901         * @config START_WEEKDAY
4902         * @type number
4903         * @default 0
4904         */ 
4905         cfg.addProperty(DEF_CFG.START_WEEKDAY.key, { value:DEF_CFG.START_WEEKDAY.value, handler:this.delegateConfig, validator:cfg.checkNumber  } );
4906         
4907         /**
4908         * True if the Calendar should show weekday labels. True by default.
4909         * @config SHOW_WEEKDAYS
4910         * @type Boolean
4911         * @default true
4912         */ 
4913         cfg.addProperty(DEF_CFG.SHOW_WEEKDAYS.key, { value:DEF_CFG.SHOW_WEEKDAYS.value, handler:this.delegateConfig, validator:cfg.checkBoolean } );
4914         
4915         /**
4916         * True if the Calendar should show week row headers. False by default.
4917         * @config SHOW_WEEK_HEADER
4918         * @type Boolean
4919         * @default false
4920         */ 
4921         cfg.addProperty(DEF_CFG.SHOW_WEEK_HEADER.key,{ value:DEF_CFG.SHOW_WEEK_HEADER.value, handler:this.delegateConfig, validator:cfg.checkBoolean } );
4922         
4923         /**
4924         * True if the Calendar should show week row footers. False by default.
4925         * @config SHOW_WEEK_FOOTER
4926         * @type Boolean
4927         * @default false
4928         */
4929         cfg.addProperty(DEF_CFG.SHOW_WEEK_FOOTER.key,{ value:DEF_CFG.SHOW_WEEK_FOOTER.value, handler:this.delegateConfig, validator:cfg.checkBoolean } );
4930         
4931         /**
4932         * True if the Calendar should suppress weeks that are not a part of the current month. False by default.
4933         * @config HIDE_BLANK_WEEKS
4934         * @type Boolean
4935         * @default false
4936         */  
4937         cfg.addProperty(DEF_CFG.HIDE_BLANK_WEEKS.key,{ value:DEF_CFG.HIDE_BLANK_WEEKS.value, handler:this.delegateConfig, validator:cfg.checkBoolean } );
4938         
4939         /**
4940         * The image that should be used for the left navigation arrow.
4941         * @config NAV_ARROW_LEFT
4942         * @type String
4943         * @deprecated You can customize the image by overriding the default CSS class for the left arrow - "calnavleft"
4944         * @default null
4945         */  
4946         cfg.addProperty(DEF_CFG.NAV_ARROW_LEFT.key, { value:DEF_CFG.NAV_ARROW_LEFT.value, handler:this.delegateConfig } );
4947         
4948         /**
4949         * The image that should be used for the right navigation arrow.
4950         * @config NAV_ARROW_RIGHT
4951         * @type String
4952         * @deprecated You can customize the image by overriding the default CSS class for the right arrow - "calnavright"
4953         * @default null
4954         */  
4955         cfg.addProperty(DEF_CFG.NAV_ARROW_RIGHT.key, { value:DEF_CFG.NAV_ARROW_RIGHT.value, handler:this.delegateConfig } );
4956     
4957         // Locale properties
4958         
4959         /**
4960         * The short month labels for the current locale.
4961         * @config MONTHS_SHORT
4962         * @type String[]
4963         * @default ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
4964         */
4965         cfg.addProperty(DEF_CFG.MONTHS_SHORT.key, { value:DEF_CFG.MONTHS_SHORT.value, handler:this.delegateConfig } );
4966         
4967         /**
4968         * The long month labels for the current locale.
4969         * @config MONTHS_LONG
4970         * @type String[]
4971         * @default ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
4972         */  
4973         cfg.addProperty(DEF_CFG.MONTHS_LONG.key,  { value:DEF_CFG.MONTHS_LONG.value, handler:this.delegateConfig } );
4974         
4975         /**
4976         * The 1-character weekday labels for the current locale.
4977         * @config WEEKDAYS_1CHAR
4978         * @type String[]
4979         * @default ["S", "M", "T", "W", "T", "F", "S"]
4980         */  
4981         cfg.addProperty(DEF_CFG.WEEKDAYS_1CHAR.key, { value:DEF_CFG.WEEKDAYS_1CHAR.value, handler:this.delegateConfig } );
4982         
4983         /**
4984         * The short weekday labels for the current locale.
4985         * @config WEEKDAYS_SHORT
4986         * @type String[]
4987         * @default ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]
4988         */  
4989         cfg.addProperty(DEF_CFG.WEEKDAYS_SHORT.key, { value:DEF_CFG.WEEKDAYS_SHORT.value, handler:this.delegateConfig } );
4990         
4991         /**
4992         * The medium weekday labels for the current locale.
4993         * @config WEEKDAYS_MEDIUM
4994         * @type String[]
4995         * @default ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
4996         */  
4997         cfg.addProperty(DEF_CFG.WEEKDAYS_MEDIUM.key, { value:DEF_CFG.WEEKDAYS_MEDIUM.value, handler:this.delegateConfig } );
4998         
4999         /**
5000         * The long weekday labels for the current locale.
5001         * @config WEEKDAYS_LONG
5002         * @type String[]
5003         * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
5004         */  
5005         cfg.addProperty(DEF_CFG.WEEKDAYS_LONG.key, { value:DEF_CFG.WEEKDAYS_LONG.value, handler:this.delegateConfig } );
5006     
5007         /**
5008         * The setting that determines which length of month labels should be used. Possible values are "short" and "long".
5009         * @config LOCALE_MONTHS
5010         * @type String
5011         * @default "long"
5012         */
5013         cfg.addProperty(DEF_CFG.LOCALE_MONTHS.key, { value:DEF_CFG.LOCALE_MONTHS.value, handler:this.delegateConfig } );
5014     
5015         /**
5016         * The setting that determines which length of weekday labels should be used. Possible values are "1char", "short", "medium", and "long".
5017         * @config LOCALE_WEEKDAYS
5018         * @type String
5019         * @default "short"
5020         */ 
5021         cfg.addProperty(DEF_CFG.LOCALE_WEEKDAYS.key, { value:DEF_CFG.LOCALE_WEEKDAYS.value, handler:this.delegateConfig } );
5022     
5023         /**
5024         * The value used to delimit individual dates in a date string passed to various Calendar functions.
5025         * @config DATE_DELIMITER
5026         * @type String
5027         * @default ","
5028         */
5029         cfg.addProperty(DEF_CFG.DATE_DELIMITER.key,  { value:DEF_CFG.DATE_DELIMITER.value, handler:this.delegateConfig } );
5030     
5031         /**
5032         * The value used to delimit date fields in a date string passed to various Calendar functions.
5033         * @config DATE_FIELD_DELIMITER
5034         * @type String
5035         * @default "/"
5036         */ 
5037         cfg.addProperty(DEF_CFG.DATE_FIELD_DELIMITER.key,{ value:DEF_CFG.DATE_FIELD_DELIMITER.value, handler:this.delegateConfig } );
5038     
5039         /**
5040         * The value used to delimit date ranges in a date string passed to various Calendar functions.
5041         * @config DATE_RANGE_DELIMITER
5042         * @type String
5043         * @default "-"
5044         */
5045         cfg.addProperty(DEF_CFG.DATE_RANGE_DELIMITER.key,{ value:DEF_CFG.DATE_RANGE_DELIMITER.value, handler:this.delegateConfig } );
5046     
5047         /**
5048         * The position of the month in a month/year date string
5049         * @config MY_MONTH_POSITION
5050         * @type Number
5051         * @default 1
5052         */
5053         cfg.addProperty(DEF_CFG.MY_MONTH_POSITION.key, { value:DEF_CFG.MY_MONTH_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
5054         
5055         /**
5056         * The position of the year in a month/year date string
5057         * @config MY_YEAR_POSITION
5058         * @type Number
5059         * @default 2
5060         */ 
5061         cfg.addProperty(DEF_CFG.MY_YEAR_POSITION.key, { value:DEF_CFG.MY_YEAR_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
5062         
5063         /**
5064         * The position of the month in a month/day date string
5065         * @config MD_MONTH_POSITION
5066         * @type Number
5067         * @default 1
5068         */ 
5069         cfg.addProperty(DEF_CFG.MD_MONTH_POSITION.key, { value:DEF_CFG.MD_MONTH_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
5070         
5071         /**
5072         * The position of the day in a month/year date string
5073         * @config MD_DAY_POSITION
5074         * @type Number
5075         * @default 2
5076         */ 
5077         cfg.addProperty(DEF_CFG.MD_DAY_POSITION.key,  { value:DEF_CFG.MD_DAY_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
5078         
5079         /**
5080         * The position of the month in a month/day/year date string
5081         * @config MDY_MONTH_POSITION
5082         * @type Number
5083         * @default 1
5084         */ 
5085         cfg.addProperty(DEF_CFG.MDY_MONTH_POSITION.key, { value:DEF_CFG.MDY_MONTH_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
5086         
5087         /**
5088         * The position of the day in a month/day/year date string
5089         * @config MDY_DAY_POSITION
5090         * @type Number
5091         * @default 2
5092         */ 
5093         cfg.addProperty(DEF_CFG.MDY_DAY_POSITION.key, { value:DEF_CFG.MDY_DAY_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
5094         
5095         /**
5096         * The position of the year in a month/day/year date string
5097         * @config MDY_YEAR_POSITION
5098         * @type Number
5099         * @default 3
5100         */ 
5101         cfg.addProperty(DEF_CFG.MDY_YEAR_POSITION.key, { value:DEF_CFG.MDY_YEAR_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
5102     
5103         /**
5104         * The position of the month in the month year label string used as the Calendar header
5105         * @config MY_LABEL_MONTH_POSITION
5106         * @type Number
5107         * @default 1
5108         */
5109         cfg.addProperty(DEF_CFG.MY_LABEL_MONTH_POSITION.key, { value:DEF_CFG.MY_LABEL_MONTH_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
5110     
5111         /**
5112         * The position of the year in the month year label string used as the Calendar header
5113         * @config MY_LABEL_YEAR_POSITION
5114         * @type Number
5115         * @default 2
5116         */
5117         cfg.addProperty(DEF_CFG.MY_LABEL_YEAR_POSITION.key, { value:DEF_CFG.MY_LABEL_YEAR_POSITION.value, handler:this.delegateConfig, validator:cfg.checkNumber } );
5118
5119         /**
5120         * The suffix used after the month when rendering the Calendar header
5121         * @config MY_LABEL_MONTH_SUFFIX
5122         * @type String
5123         * @default " "
5124         */
5125         cfg.addProperty(DEF_CFG.MY_LABEL_MONTH_SUFFIX.key, { value:DEF_CFG.MY_LABEL_MONTH_SUFFIX.value, handler:this.delegateConfig } );
5126         
5127         /**
5128         * The suffix used after the year when rendering the Calendar header
5129         * @config MY_LABEL_YEAR_SUFFIX
5130         * @type String
5131         * @default ""
5132         */
5133         cfg.addProperty(DEF_CFG.MY_LABEL_YEAR_SUFFIX.key, { value:DEF_CFG.MY_LABEL_YEAR_SUFFIX.value, handler:this.delegateConfig } );
5134
5135         /**
5136         * Configuration for the Month Year Navigation UI. By default it is disabled
5137         * @config NAV
5138         * @type Object
5139         * @default null
5140         */
5141         cfg.addProperty(DEF_CFG.NAV.key, { value:DEF_CFG.NAV.value, handler:this.configNavigator } );
5142
5143         /**
5144          * The map of UI strings which the CalendarGroup UI uses.
5145          *
5146          * @config strings
5147          * @type {Object}
5148          * @default An object with the properties shown below:
5149          *     <dl>
5150          *         <dt>previousMonth</dt><dd><em>String</em> : The string to use for the "Previous Month" navigation UI. Defaults to "Previous Month".</dd>
5151          *         <dt>nextMonth</dt><dd><em>String</em> : The string to use for the "Next Month" navigation UI. Defaults to "Next Month".</dd>
5152          *         <dt>close</dt><dd><em>String</em> : The string to use for the close button label. Defaults to "Close".</dd>
5153          *     </dl>
5154          */
5155         cfg.addProperty(DEF_CFG.STRINGS.key, { 
5156             value:DEF_CFG.STRINGS.value, 
5157             handler:this.configStrings, 
5158             validator: function(val) {
5159                 return Lang.isObject(val);
5160             },
5161             supercedes: DEF_CFG.STRINGS.supercedes
5162         });
5163     },
5164
5165     /**
5166     * Initializes CalendarGroup's built-in CustomEvents
5167     * @method initEvents
5168     */
5169     initEvents : function() {
5170
5171         var me = this,
5172             strEvent = "Event",
5173             CE = YAHOO.util.CustomEvent;
5174
5175         /**
5176         * Proxy subscriber to subscribe to the CalendarGroup's child Calendars' CustomEvents
5177         * @method sub
5178         * @private
5179         * @param {Function} fn The function to subscribe to this CustomEvent
5180         * @param {Object} obj The CustomEvent's scope object
5181         * @param {Boolean} bOverride Whether or not to apply scope correction
5182         */
5183         var sub = function(fn, obj, bOverride) {
5184             for (var p=0;p<me.pages.length;++p) {
5185                 var cal = me.pages[p];
5186                 cal[this.type + strEvent].subscribe(fn, obj, bOverride);
5187             }
5188         };
5189
5190         /**
5191         * Proxy unsubscriber to unsubscribe from the CalendarGroup's child Calendars' CustomEvents
5192         * @method unsub
5193         * @private
5194         * @param {Function} fn The function to subscribe to this CustomEvent
5195         * @param {Object} obj The CustomEvent's scope object
5196         */
5197         var unsub = function(fn, obj) {
5198             for (var p=0;p<me.pages.length;++p) {
5199                 var cal = me.pages[p];
5200                 cal[this.type + strEvent].unsubscribe(fn, obj);
5201             }
5202         };
5203
5204         var defEvents = Calendar._EVENT_TYPES;
5205
5206         /**
5207         * Fired before a date selection is made
5208         * @event beforeSelectEvent
5209         */
5210         me.beforeSelectEvent = new CE(defEvents.BEFORE_SELECT);
5211         me.beforeSelectEvent.subscribe = sub; me.beforeSelectEvent.unsubscribe = unsub;
5212
5213         /**
5214         * Fired when a date selection is made
5215         * @event selectEvent
5216         * @param {Array} Array of Date field arrays in the format [YYYY, MM, DD].
5217         */
5218         me.selectEvent = new CE(defEvents.SELECT); 
5219         me.selectEvent.subscribe = sub; me.selectEvent.unsubscribe = unsub;
5220
5221         /**
5222         * Fired before a date or set of dates is deselected
5223         * @event beforeDeselectEvent
5224         */
5225         me.beforeDeselectEvent = new CE(defEvents.BEFORE_DESELECT); 
5226         me.beforeDeselectEvent.subscribe = sub; me.beforeDeselectEvent.unsubscribe = unsub;
5227
5228         /**
5229         * Fired when a date or set of dates has been deselected
5230         * @event deselectEvent
5231         * @param {Array} Array of Date field arrays in the format [YYYY, MM, DD].
5232         */
5233         me.deselectEvent = new CE(defEvents.DESELECT); 
5234         me.deselectEvent.subscribe = sub; me.deselectEvent.unsubscribe = unsub;
5235         
5236         /**
5237         * Fired when the Calendar page is changed
5238         * @event changePageEvent
5239         */
5240         me.changePageEvent = new CE(defEvents.CHANGE_PAGE); 
5241         me.changePageEvent.subscribe = sub; me.changePageEvent.unsubscribe = unsub;
5242
5243         /**
5244         * Fired before the Calendar is rendered
5245         * @event beforeRenderEvent
5246         */
5247         me.beforeRenderEvent = new CE(defEvents.BEFORE_RENDER);
5248         me.beforeRenderEvent.subscribe = sub; me.beforeRenderEvent.unsubscribe = unsub;
5249     
5250         /**
5251         * Fired when the Calendar is rendered
5252         * @event renderEvent
5253         */
5254         me.renderEvent = new CE(defEvents.RENDER);
5255         me.renderEvent.subscribe = sub; me.renderEvent.unsubscribe = unsub;
5256     
5257         /**
5258         * Fired when the Calendar is reset
5259         * @event resetEvent
5260         */
5261         me.resetEvent = new CE(defEvents.RESET); 
5262         me.resetEvent.subscribe = sub; me.resetEvent.unsubscribe = unsub;
5263     
5264         /**
5265         * Fired when the Calendar is cleared
5266         * @event clearEvent
5267         */
5268         me.clearEvent = new CE(defEvents.CLEAR);
5269         me.clearEvent.subscribe = sub; me.clearEvent.unsubscribe = unsub;
5270
5271         /**
5272         * Fired just before the CalendarGroup is to be shown
5273         * @event beforeShowEvent
5274         */
5275         me.beforeShowEvent = new CE(defEvents.BEFORE_SHOW);
5276     
5277         /**
5278         * Fired after the CalendarGroup is shown
5279         * @event showEvent
5280         */
5281         me.showEvent = new CE(defEvents.SHOW);
5282     
5283         /**
5284         * Fired just before the CalendarGroup is to be hidden
5285         * @event beforeHideEvent
5286         */
5287         me.beforeHideEvent = new CE(defEvents.BEFORE_HIDE);
5288     
5289         /**
5290         * Fired after the CalendarGroup is hidden
5291         * @event hideEvent
5292         */
5293         me.hideEvent = new CE(defEvents.HIDE);
5294
5295         /**
5296         * Fired just before the CalendarNavigator is to be shown
5297         * @event beforeShowNavEvent
5298         */
5299         me.beforeShowNavEvent = new CE(defEvents.BEFORE_SHOW_NAV);
5300     
5301         /**
5302         * Fired after the CalendarNavigator is shown
5303         * @event showNavEvent
5304         */
5305         me.showNavEvent = new CE(defEvents.SHOW_NAV);
5306     
5307         /**
5308         * Fired just before the CalendarNavigator is to be hidden
5309         * @event beforeHideNavEvent
5310         */
5311         me.beforeHideNavEvent = new CE(defEvents.BEFORE_HIDE_NAV);
5312
5313         /**
5314         * Fired after the CalendarNavigator is hidden
5315         * @event hideNavEvent
5316         */
5317         me.hideNavEvent = new CE(defEvents.HIDE_NAV);
5318
5319         /**
5320         * Fired just before the CalendarNavigator is to be rendered
5321         * @event beforeRenderNavEvent
5322         */
5323         me.beforeRenderNavEvent = new CE(defEvents.BEFORE_RENDER_NAV);
5324
5325         /**
5326         * Fired after the CalendarNavigator is rendered
5327         * @event renderNavEvent
5328         */
5329         me.renderNavEvent = new CE(defEvents.RENDER_NAV);
5330
5331         /**
5332         * Fired just before the CalendarGroup is to be destroyed
5333         * @event beforeDestroyEvent
5334         */
5335         me.beforeDestroyEvent = new CE(defEvents.BEFORE_DESTROY);
5336
5337         /**
5338         * Fired after the CalendarGroup is destroyed. This event should be used
5339         * for notification only. When this event is fired, important CalendarGroup instance
5340         * properties, dom references and event listeners have already been 
5341         * removed/dereferenced, and hence the CalendarGroup instance is not in a usable 
5342         * state.
5343         *
5344         * @event destroyEvent
5345         */
5346         me.destroyEvent = new CE(defEvents.DESTROY);
5347     },
5348     
5349     /**
5350     * The default Config handler for the "pages" property
5351     * @method configPages
5352     * @param {String} type The CustomEvent type (usually the property name)
5353     * @param {Object[]} args The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
5354     * @param {Object} obj The scope object. For configuration handlers, this will usually equal the owner.
5355     */
5356     configPages : function(type, args, obj) {
5357         var pageCount = args[0],
5358             cfgPageDate = DEF_CFG.PAGEDATE.key,
5359             sep = "_",
5360             caldate,
5361             firstPageDate = null,
5362             groupCalClass = "groupcal",
5363             firstClass = "first-of-type",
5364             lastClass = "last-of-type";
5365
5366         for (var p=0;p<pageCount;++p) {
5367             var calId = this.id + sep + p,
5368                 calContainerId = this.containerId + sep + p,
5369                 childConfig = this.cfg.getConfig();
5370
5371             childConfig.close = false;
5372             childConfig.title = false;
5373             childConfig.navigator = null;
5374
5375             if (p > 0) {
5376                 caldate = new Date(firstPageDate);
5377                 this._setMonthOnDate(caldate, caldate.getMonth() + p);
5378                 childConfig.pageDate = caldate;
5379             }
5380
5381             var cal = this.constructChild(calId, calContainerId, childConfig);
5382
5383             Dom.removeClass(cal.oDomContainer, this.Style.CSS_SINGLE);
5384             Dom.addClass(cal.oDomContainer, groupCalClass);
5385
5386             if (p===0) {
5387                 firstPageDate = cal.cfg.getProperty(cfgPageDate);
5388                 Dom.addClass(cal.oDomContainer, firstClass);
5389             }
5390     
5391             if (p==(pageCount-1)) {
5392                 Dom.addClass(cal.oDomContainer, lastClass);
5393             }
5394     
5395             cal.parent = this;
5396             cal.index = p; 
5397     
5398             this.pages[this.pages.length] = cal;
5399         }
5400     },
5401     
5402     /**
5403     * The default Config handler for the "pagedate" property
5404     * @method configPageDate
5405     * @param {String} type The CustomEvent type (usually the property name)
5406     * @param {Object[]} args The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
5407     * @param {Object} obj The scope object. For configuration handlers, this will usually equal the owner.
5408     */
5409     configPageDate : function(type, args, obj) {
5410         var val = args[0],
5411             firstPageDate;
5412
5413         var cfgPageDate = DEF_CFG.PAGEDATE.key;
5414         
5415         for (var p=0;p<this.pages.length;++p) {
5416             var cal = this.pages[p];
5417             if (p === 0) {
5418                 firstPageDate = cal._parsePageDate(val);
5419                 cal.cfg.setProperty(cfgPageDate, firstPageDate);
5420             } else {
5421                 var pageDate = new Date(firstPageDate);
5422                 this._setMonthOnDate(pageDate, pageDate.getMonth() + p);
5423                 cal.cfg.setProperty(cfgPageDate, pageDate);
5424             }
5425         }
5426     },
5427     
5428     /**
5429     * The default Config handler for the CalendarGroup "selected" property
5430     * @method configSelected
5431     * @param {String} type The CustomEvent type (usually the property name)
5432     * @param {Object[]} args The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
5433     * @param {Object} obj The scope object. For configuration handlers, this will usually equal the owner.
5434     */
5435     configSelected : function(type, args, obj) {
5436         var cfgSelected = DEF_CFG.SELECTED.key;
5437         this.delegateConfig(type, args, obj);
5438         var selected = (this.pages.length > 0) ? this.pages[0].cfg.getProperty(cfgSelected) : []; 
5439         this.cfg.setProperty(cfgSelected, selected, true);
5440     },
5441
5442     
5443     /**
5444     * Delegates a configuration property to the CustomEvents associated with the CalendarGroup's children
5445     * @method delegateConfig
5446     * @param {String} type The CustomEvent type (usually the property name)
5447     * @param {Object[]} args The CustomEvent arguments. For configuration handlers, args[0] will equal the newly applied value for the property.
5448     * @param {Object} obj The scope object. For configuration handlers, this will usually equal the owner.
5449     */
5450     delegateConfig : function(type, args, obj) {
5451         var val = args[0];
5452         var cal;
5453     
5454         for (var p=0;p<this.pages.length;p++) {
5455             cal = this.pages[p];
5456             cal.cfg.setProperty(type, val);
5457         }
5458     },
5459
5460     /**
5461     * Adds a function to all child Calendars within this CalendarGroup.
5462     * @method setChildFunction
5463     * @param {String}  fnName  The name of the function
5464     * @param {Function}  fn   The function to apply to each Calendar page object
5465     */
5466     setChildFunction : function(fnName, fn) {
5467         var pageCount = this.cfg.getProperty(DEF_CFG.PAGES.key);
5468     
5469         for (var p=0;p<pageCount;++p) {
5470             this.pages[p][fnName] = fn;
5471         }
5472     },
5473
5474     /**
5475     * Calls a function within all child Calendars within this CalendarGroup.
5476     * @method callChildFunction
5477     * @param {String}  fnName  The name of the function
5478     * @param {Array}  args  The arguments to pass to the function
5479     */
5480     callChildFunction : function(fnName, args) {
5481         var pageCount = this.cfg.getProperty(DEF_CFG.PAGES.key);
5482
5483         for (var p=0;p<pageCount;++p) {
5484             var page = this.pages[p];
5485             if (page[fnName]) {
5486                 var fn = page[fnName];
5487                 fn.call(page, args);
5488             }
5489         } 
5490     },
5491
5492     /**
5493     * Constructs a child calendar. This method can be overridden if a subclassed version of the default
5494     * calendar is to be used.
5495     * @method constructChild
5496     * @param {String} id   The id of the table element that will represent the calendar widget
5497     * @param {String} containerId The id of the container div element that will wrap the calendar table
5498     * @param {Object} config  The configuration object containing the Calendar's arguments
5499     * @return {YAHOO.widget.Calendar} The YAHOO.widget.Calendar instance that is constructed
5500     */
5501     constructChild : function(id,containerId,config) {
5502         var container = document.getElementById(containerId);
5503         if (! container) {
5504             container = document.createElement("div");
5505             container.id = containerId;
5506             this.oDomContainer.appendChild(container);
5507         }
5508         return new Calendar(id,containerId,config);
5509     },
5510     
5511     /**
5512     * Sets the calendar group's month explicitly. This month will be set into the first
5513     * page of the multi-page calendar, and all other months will be iterated appropriately.
5514     * @method setMonth
5515     * @param {Number} month  The numeric month, from 0 (January) to 11 (December)
5516     */
5517     setMonth : function(month) {
5518         month = parseInt(month, 10);
5519         var currYear;
5520
5521         var cfgPageDate = DEF_CFG.PAGEDATE.key;
5522
5523         for (var p=0; p<this.pages.length; ++p) {
5524             var cal = this.pages[p];
5525             var pageDate = cal.cfg.getProperty(cfgPageDate);
5526             if (p === 0) {
5527                 currYear = pageDate.getFullYear();
5528             } else {
5529                 pageDate.setFullYear(currYear);
5530             }
5531             this._setMonthOnDate(pageDate, month+p); 
5532             cal.cfg.setProperty(cfgPageDate, pageDate);
5533         }
5534     },
5535
5536     /**
5537     * Sets the calendar group's year explicitly. This year will be set into the first
5538     * page of the multi-page calendar, and all other months will be iterated appropriately.
5539     * @method setYear
5540     * @param {Number} year  The numeric 4-digit year
5541     */
5542     setYear : function(year) {
5543     
5544         var cfgPageDate = DEF_CFG.PAGEDATE.key;
5545     
5546         year = parseInt(year, 10);
5547         for (var p=0;p<this.pages.length;++p) {
5548             var cal = this.pages[p];
5549             var pageDate = cal.cfg.getProperty(cfgPageDate);
5550     
5551             if ((pageDate.getMonth()+1) == 1 && p>0) {
5552                 year+=1;
5553             }
5554             cal.setYear(year);
5555         }
5556     },
5557
5558     /**
5559     * Calls the render function of all child calendars within the group.
5560     * @method render
5561     */
5562     render : function() {
5563         this.renderHeader();
5564         for (var p=0;p<this.pages.length;++p) {
5565             var cal = this.pages[p];
5566             cal.render();
5567         }
5568         this.renderFooter();
5569     },
5570
5571     /**
5572     * Selects a date or a collection of dates on the current calendar. This method, by default,
5573     * does not call the render method explicitly. Once selection has completed, render must be 
5574     * called for the changes to be reflected visually.
5575     * @method select
5576     * @param    {String/Date/Date[]}    date    The date string of dates to select in the current calendar. Valid formats are
5577     *                               individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
5578     *                               Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
5579     *                               This method can also take a JavaScript Date object or an array of Date objects.
5580     * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
5581     */
5582     select : function(date) {
5583         for (var p=0;p<this.pages.length;++p) {
5584             var cal = this.pages[p];
5585             cal.select(date);
5586         }
5587         return this.getSelectedDates();
5588     },
5589
5590     /**
5591     * Selects dates in the CalendarGroup based on the cell index provided. This method is used to select cells without having to do a full render. The selected style is applied to the cells directly.
5592     * The value of the MULTI_SELECT Configuration attribute will determine the set of dates which get selected. 
5593     * <ul>
5594     *    <li>If MULTI_SELECT is false, selectCell will select the cell at the specified index for only the last displayed Calendar page.</li>
5595     *    <li>If MULTI_SELECT is true, selectCell will select the cell at the specified index, on each displayed Calendar page.</li>
5596     * </ul>
5597     * @method selectCell
5598     * @param {Number} cellIndex The index of the cell to be selected. 
5599     * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
5600     */
5601     selectCell : function(cellIndex) {
5602         for (var p=0;p<this.pages.length;++p) {
5603             var cal = this.pages[p];
5604             cal.selectCell(cellIndex);
5605         }
5606         return this.getSelectedDates();
5607     },
5608     
5609     /**
5610     * Deselects a date or a collection of dates on the current calendar. This method, by default,
5611     * does not call the render method explicitly. Once deselection has completed, render must be 
5612     * called for the changes to be reflected visually.
5613     * @method deselect
5614     * @param {String/Date/Date[]} date The date string of dates to deselect in the current calendar. Valid formats are
5615     *        individual date(s) (12/24/2005,12/26/2005) or date range(s) (12/24/2005-1/1/2006).
5616     *        Multiple comma-delimited dates can also be passed to this method (12/24/2005,12/11/2005-12/13/2005).
5617     *        This method can also take a JavaScript Date object or an array of Date objects. 
5618     * @return {Date[]}   Array of JavaScript Date objects representing all individual dates that are currently selected.
5619     */
5620     deselect : function(date) {
5621         for (var p=0;p<this.pages.length;++p) {
5622             var cal = this.pages[p];
5623             cal.deselect(date);
5624         }
5625         return this.getSelectedDates();
5626     },
5627     
5628     /**
5629     * Deselects all dates on the current calendar.
5630     * @method deselectAll
5631     * @return {Date[]}  Array of JavaScript Date objects representing all individual dates that are currently selected.
5632     *      Assuming that this function executes properly, the return value should be an empty array.
5633     *      However, the empty array is returned for the sake of being able to check the selection status
5634     *      of the calendar.
5635     */
5636     deselectAll : function() {
5637         for (var p=0;p<this.pages.length;++p) {
5638             var cal = this.pages[p];
5639             cal.deselectAll();
5640         }
5641         return this.getSelectedDates();
5642     },
5643
5644     /**
5645     * Deselects dates in the CalendarGroup based on the cell index provided. This method is used to select cells without having to do a full render. The selected style is applied to the cells directly.
5646     * deselectCell will deselect the cell at the specified index on each displayed Calendar page.
5647     *
5648     * @method deselectCell
5649     * @param {Number} cellIndex The index of the cell to deselect. 
5650     * @return {Date[]} Array of JavaScript Date objects representing all individual dates that are currently selected.
5651     */
5652     deselectCell : function(cellIndex) {
5653         for (var p=0;p<this.pages.length;++p) {
5654             var cal = this.pages[p];
5655             cal.deselectCell(cellIndex);
5656         }
5657         return this.getSelectedDates();
5658     },
5659
5660     /**
5661     * Resets the calendar widget to the originally selected month and year, and 
5662     * sets the calendar to the initial selection(s).
5663     * @method reset
5664     */
5665     reset : function() {
5666         for (var p=0;p<this.pages.length;++p) {
5667             var cal = this.pages[p];
5668             cal.reset();
5669         }
5670     },
5671
5672     /**
5673     * Clears the selected dates in the current calendar widget and sets the calendar
5674     * to the current month and year.
5675     * @method clear
5676     */
5677     clear : function() {
5678         for (var p=0;p<this.pages.length;++p) {
5679             var cal = this.pages[p];
5680             cal.clear();
5681         }
5682
5683         this.cfg.setProperty(DEF_CFG.SELECTED.key, []);
5684         this.cfg.setProperty(DEF_CFG.PAGEDATE.key, new Date(this.pages[0].today.getTime()));
5685         this.render();
5686     },
5687
5688     /**
5689     * Navigates to the next month page in the calendar widget.
5690     * @method nextMonth
5691     */
5692     nextMonth : function() {
5693         for (var p=0;p<this.pages.length;++p) {
5694             var cal = this.pages[p];
5695             cal.nextMonth();
5696         }
5697     },
5698     
5699     /**
5700     * Navigates to the previous month page in the calendar widget.
5701     * @method previousMonth
5702     */
5703     previousMonth : function() {
5704         for (var p=this.pages.length-1;p>=0;--p) {
5705             var cal = this.pages[p];
5706             cal.previousMonth();
5707         }
5708     },
5709     
5710     /**
5711     * Navigates to the next year in the currently selected month in the calendar widget.
5712     * @method nextYear
5713     */
5714     nextYear : function() {
5715         for (var p=0;p<this.pages.length;++p) {
5716             var cal = this.pages[p];
5717             cal.nextYear();
5718         }
5719     },
5720
5721     /**
5722     * Navigates to the previous year in the currently selected month in the calendar widget.
5723     * @method previousYear
5724     */
5725     previousYear : function() {
5726         for (var p=0;p<this.pages.length;++p) {
5727             var cal = this.pages[p];
5728             cal.previousYear();
5729         }
5730     },
5731
5732     /**
5733     * Gets the list of currently selected dates from the calendar.
5734     * @return   An array of currently selected JavaScript Date objects.
5735     * @type Date[]
5736     */
5737     getSelectedDates : function() { 
5738         var returnDates = [];
5739         var selected = this.cfg.getProperty(DEF_CFG.SELECTED.key);
5740         for (var d=0;d<selected.length;++d) {
5741             var dateArray = selected[d];
5742
5743             var date = DateMath.getDate(dateArray[0],dateArray[1]-1,dateArray[2]);
5744             returnDates.push(date);
5745         }
5746
5747         returnDates.sort( function(a,b) { return a-b; } );
5748         return returnDates;
5749     },
5750
5751     /**
5752     * Adds a renderer to the render stack. The function reference passed to this method will be executed
5753     * when a date cell matches the conditions specified in the date string for this renderer.
5754     * @method addRenderer
5755     * @param {String} sDates  A date string to associate with the specified renderer. Valid formats
5756     *         include date (12/24/2005), month/day (12/24), and range (12/1/2004-1/1/2005)
5757     * @param {Function} fnRender The function executed to render cells that match the render rules for this renderer.
5758     */
5759     addRenderer : function(sDates, fnRender) {
5760         for (var p=0;p<this.pages.length;++p) {
5761             var cal = this.pages[p];
5762             cal.addRenderer(sDates, fnRender);
5763         }
5764     },
5765
5766     /**
5767     * Adds a month to the render stack. The function reference passed to this method will be executed
5768     * when a date cell matches the month passed to this method.
5769     * @method addMonthRenderer
5770     * @param {Number} month  The month (1-12) to associate with this renderer
5771     * @param {Function} fnRender The function executed to render cells that match the render rules for this renderer.
5772     */
5773     addMonthRenderer : function(month, fnRender) {
5774         for (var p=0;p<this.pages.length;++p) {
5775             var cal = this.pages[p];
5776             cal.addMonthRenderer(month, fnRender);
5777         }
5778     },
5779
5780     /**
5781     * Adds a weekday to the render stack. The function reference passed to this method will be executed
5782     * when a date cell matches the weekday passed to this method.
5783     * @method addWeekdayRenderer
5784     * @param {Number} weekday  The weekday (1-7) to associate with this renderer. 1=Sunday, 2=Monday etc.
5785     * @param {Function} fnRender The function executed to render cells that match the render rules for this renderer.
5786     */
5787     addWeekdayRenderer : function(weekday, fnRender) {
5788         for (var p=0;p<this.pages.length;++p) {
5789             var cal = this.pages[p];
5790             cal.addWeekdayRenderer(weekday, fnRender);
5791         }
5792     },
5793
5794     /**
5795      * Removes all custom renderers added to the CalendarGroup through the addRenderer, addMonthRenderer and 
5796      * addWeekRenderer methods. CalendarGroup's render method needs to be called to after removing renderers 
5797      * to see the changes applied.
5798      * 
5799      * @method removeRenderers
5800      */
5801     removeRenderers : function() {
5802         this.callChildFunction("removeRenderers");
5803     },
5804
5805     /**
5806     * Renders the header for the CalendarGroup.
5807     * @method renderHeader
5808     */
5809     renderHeader : function() {
5810         // EMPTY DEFAULT IMPL
5811     },
5812
5813     /**
5814     * Renders a footer for the 2-up calendar container. By default, this method is
5815     * unimplemented.
5816     * @method renderFooter
5817     */
5818     renderFooter : function() {
5819         // EMPTY DEFAULT IMPL
5820     },
5821
5822     /**
5823     * Adds the designated number of months to the current calendar month, and sets the current
5824     * calendar page date to the new month.
5825     * @method addMonths
5826     * @param {Number} count The number of months to add to the current calendar
5827     */
5828     addMonths : function(count) {
5829         this.callChildFunction("addMonths", count);
5830     },
5831     
5832     /**
5833     * Subtracts the designated number of months from the current calendar month, and sets the current
5834     * calendar page date to the new month.
5835     * @method subtractMonths
5836     * @param {Number} count The number of months to subtract from the current calendar
5837     */
5838     subtractMonths : function(count) {
5839         this.callChildFunction("subtractMonths", count);
5840     },
5841
5842     /**
5843     * Adds the designated number of years to the current calendar, and sets the current
5844     * calendar page date to the new month.
5845     * @method addYears
5846     * @param {Number} count The number of years to add to the current calendar
5847     */
5848     addYears : function(count) {
5849         this.callChildFunction("addYears", count);
5850     },
5851
5852     /**
5853     * Subtcats the designated number of years from the current calendar, and sets the current
5854     * calendar page date to the new month.
5855     * @method subtractYears
5856     * @param {Number} count The number of years to subtract from the current calendar
5857     */
5858     subtractYears : function(count) {
5859         this.callChildFunction("subtractYears", count);
5860     },
5861
5862     /**
5863      * Returns the Calendar page instance which has a pagedate (month/year) matching the given date. 
5864      * Returns null if no match is found.
5865      * 
5866      * @method getCalendarPage
5867      * @param {Date} date The JavaScript Date object for which a Calendar page is to be found.
5868      * @return {Calendar} The Calendar page instance representing the month to which the date 
5869      * belongs.
5870      */
5871     getCalendarPage : function(date) {
5872         var cal = null;
5873         if (date) {
5874             var y = date.getFullYear(),
5875                 m = date.getMonth();
5876
5877             var pages = this.pages;
5878             for (var i = 0; i < pages.length; ++i) {
5879                 var pageDate = pages[i].cfg.getProperty("pagedate");
5880                 if (pageDate.getFullYear() === y && pageDate.getMonth() === m) {
5881                     cal = pages[i];
5882                     break;
5883                 }
5884             }
5885         }
5886         return cal;
5887     },
5888
5889     /**
5890     * Sets the month on a Date object, taking into account year rollover if the month is less than 0 or greater than 11.
5891     * The Date object passed in is modified. It should be cloned before passing it into this method if the original value needs to be maintained
5892     * @method _setMonthOnDate
5893     * @private
5894     * @param {Date} date The Date object on which to set the month index
5895     * @param {Number} iMonth The month index to set
5896     */
5897     _setMonthOnDate : function(date, iMonth) {
5898         // Bug in Safari 1.3, 2.0 (WebKit build < 420), Date.setMonth does not work consistently if iMonth is not 0-11
5899         if (YAHOO.env.ua.webkit && YAHOO.env.ua.webkit < 420 && (iMonth < 0 || iMonth > 11)) {
5900             var newDate = DateMath.add(date, DateMath.MONTH, iMonth-date.getMonth());
5901             date.setTime(newDate.getTime());
5902         } else {
5903             date.setMonth(iMonth);
5904         }
5905     },
5906     
5907     /**
5908      * Fixes the width of the CalendarGroup container element, to account for miswrapped floats
5909      * @method _fixWidth
5910      * @private
5911      */
5912     _fixWidth : function() {
5913         var w = 0;
5914         for (var p=0;p<this.pages.length;++p) {
5915             var cal = this.pages[p];
5916             w += cal.oDomContainer.offsetWidth;
5917         }
5918         if (w > 0) {
5919             this.oDomContainer.style.width = w + "px";
5920         }
5921     },
5922     
5923     /**
5924     * Returns a string representation of the object.
5925     * @method toString
5926     * @return {String} A string representation of the CalendarGroup object.
5927     */
5928     toString : function() {
5929         return "CalendarGroup " + this.id;
5930     },
5931
5932     /**
5933      * Destroys the CalendarGroup instance. The method will remove references
5934      * to HTML elements, remove any event listeners added by the CalendarGroup.
5935      * 
5936      * It will also destroy the Config and CalendarNavigator instances created by the 
5937      * CalendarGroup and the individual Calendar instances created for each page.
5938      *
5939      * @method destroy
5940      */
5941     destroy : function() {
5942
5943         if (this.beforeDestroyEvent.fire()) {
5944
5945             var cal = this;
5946     
5947             // Child objects
5948             if (cal.navigator) {
5949                 cal.navigator.destroy();
5950             }
5951     
5952             if (cal.cfg) {
5953                 cal.cfg.destroy();
5954             }
5955     
5956             // DOM event listeners
5957             Event.purgeElement(cal.oDomContainer, true);
5958     
5959             // Generated markup/DOM - Not removing the container DIV since we didn't create it.
5960             Dom.removeClass(cal.oDomContainer, CalendarGroup.CSS_CONTAINER);
5961             Dom.removeClass(cal.oDomContainer, CalendarGroup.CSS_MULTI_UP);
5962             
5963             for (var i = 0, l = cal.pages.length; i < l; i++) {
5964                 cal.pages[i].destroy();
5965                 cal.pages[i] = null;
5966             }
5967     
5968             cal.oDomContainer.innerHTML = "";
5969     
5970             // JS-to-DOM references
5971             cal.oDomContainer = null;
5972     
5973             this.destroyEvent.fire();
5974         }
5975     }
5976 };
5977
5978 /**
5979 * CSS class representing the container for the calendar
5980 * @property YAHOO.widget.CalendarGroup.CSS_CONTAINER
5981 * @static
5982 * @final
5983 * @type String
5984 */
5985 CalendarGroup.CSS_CONTAINER = "yui-calcontainer";
5986
5987 /**
5988 * CSS class representing the container for the calendar
5989 * @property YAHOO.widget.CalendarGroup.CSS_MULTI_UP
5990 * @static
5991 * @final
5992 * @type String
5993 */
5994 CalendarGroup.CSS_MULTI_UP = "multi";
5995
5996 /**
5997 * CSS class representing the title for the 2-up calendar
5998 * @property YAHOO.widget.CalendarGroup.CSS_2UPTITLE
5999 * @static
6000 * @final
6001 * @type String
6002 */
6003 CalendarGroup.CSS_2UPTITLE = "title";
6004
6005 /**
6006 * CSS class representing the close icon for the 2-up calendar
6007 * @property YAHOO.widget.CalendarGroup.CSS_2UPCLOSE
6008 * @static
6009 * @final
6010 * @deprecated Along with Calendar.IMG_ROOT and NAV_ARROW_LEFT, NAV_ARROW_RIGHT configuration properties.
6011 *     Calendar's <a href="YAHOO.widget.Calendar.html#Style.CSS_CLOSE">Style.CSS_CLOSE</a> property now represents the CSS class used to render the close icon
6012 * @type String
6013 */
6014 CalendarGroup.CSS_2UPCLOSE = "close-icon";
6015
6016 YAHOO.lang.augmentProto(CalendarGroup, Calendar, "buildDayLabel",
6017                                                  "buildMonthLabel",
6018                                                  "renderOutOfBoundsDate",
6019                                                  "renderRowHeader",
6020                                                  "renderRowFooter",
6021                                                  "renderCellDefault",
6022                                                  "styleCellDefault",
6023                                                  "renderCellStyleHighlight1",
6024                                                  "renderCellStyleHighlight2",
6025                                                  "renderCellStyleHighlight3",
6026                                                  "renderCellStyleHighlight4",
6027                                                  "renderCellStyleToday",
6028                                                  "renderCellStyleSelected",
6029                                                  "renderCellNotThisMonth",
6030                                                  "renderBodyCellRestricted",
6031                                                  "initStyles",
6032                                                  "configTitle",
6033                                                  "configClose",
6034                                                  "configIframe",
6035                                                  "configStrings",
6036                                                  "configToday",
6037                                                  "configNavigator",
6038                                                  "createTitleBar",
6039                                                  "createCloseButton",
6040                                                  "removeTitleBar",
6041                                                  "removeCloseButton",
6042                                                  "hide",
6043                                                  "show",
6044                                                  "toDate",
6045                                                  "_toDate",
6046                                                  "_parseArgs",
6047                                                  "browser");
6048
6049 YAHOO.widget.CalGrp = CalendarGroup;
6050 YAHOO.widget.CalendarGroup = CalendarGroup;
6051
6052 /**
6053 * @class YAHOO.widget.Calendar2up
6054 * @extends YAHOO.widget.CalendarGroup
6055 * @deprecated The old Calendar2up class is no longer necessary, since CalendarGroup renders in a 2up view by default.
6056 */
6057 YAHOO.widget.Calendar2up = function(id, containerId, config) {
6058     this.init(id, containerId, config);
6059 };
6060
6061 YAHOO.extend(YAHOO.widget.Calendar2up, CalendarGroup);
6062
6063 /**
6064 * @deprecated The old Calendar2up class is no longer necessary, since CalendarGroup renders in a 2up view by default.
6065 */
6066 YAHOO.widget.Cal2up = YAHOO.widget.Calendar2up;
6067
6068 })();
6069 /**
6070  * The CalendarNavigator is used along with a Calendar/CalendarGroup to 
6071  * provide a Month/Year popup navigation control, allowing the user to navigate 
6072  * to a specific month/year in the Calendar/CalendarGroup without having to 
6073  * scroll through months sequentially
6074  *
6075  * @namespace YAHOO.widget
6076  * @class CalendarNavigator
6077  * @constructor
6078  * @param {Calendar|CalendarGroup} cal The instance of the Calendar or CalendarGroup to which this CalendarNavigator should be attached.
6079  */
6080 YAHOO.widget.CalendarNavigator = function(cal) {
6081     this.init(cal);
6082 };
6083
6084 (function() {
6085     // Setup static properties (inside anon fn, so that we can use shortcuts)
6086     var CN = YAHOO.widget.CalendarNavigator;
6087
6088     /**
6089      * YAHOO.widget.CalendarNavigator.CLASSES contains constants
6090      * for the class values applied to the CalendarNaviatgator's 
6091      * DOM elements
6092      * @property YAHOO.widget.CalendarNavigator.CLASSES
6093      * @type Object
6094      * @static
6095      */
6096     CN.CLASSES = {
6097         /**
6098          * Class applied to the Calendar Navigator's bounding box
6099          * @property YAHOO.widget.CalendarNavigator.CLASSES.NAV
6100          * @type String
6101          * @static
6102          */
6103         NAV :"yui-cal-nav",
6104         /**
6105          * Class applied to the Calendar/CalendarGroup's bounding box to indicate
6106          * the Navigator is currently visible
6107          * @property YAHOO.widget.CalendarNavigator.CLASSES.NAV_VISIBLE
6108          * @type String
6109          * @static
6110          */
6111         NAV_VISIBLE: "yui-cal-nav-visible",
6112         /**
6113          * Class applied to the Navigator mask's bounding box
6114          * @property YAHOO.widget.CalendarNavigator.CLASSES.MASK
6115          * @type String
6116          * @static
6117          */
6118         MASK : "yui-cal-nav-mask",
6119         /**
6120          * Class applied to the year label/control bounding box
6121          * @property YAHOO.widget.CalendarNavigator.CLASSES.YEAR
6122          * @type String
6123          * @static
6124          */
6125         YEAR : "yui-cal-nav-y",
6126         /**
6127          * Class applied to the month label/control bounding box
6128          * @property YAHOO.widget.CalendarNavigator.CLASSES.MONTH
6129          * @type String
6130          * @static
6131          */
6132         MONTH : "yui-cal-nav-m",
6133         /**
6134          * Class applied to the submit/cancel button's bounding box
6135          * @property YAHOO.widget.CalendarNavigator.CLASSES.BUTTONS
6136          * @type String
6137          * @static
6138          */
6139         BUTTONS : "yui-cal-nav-b",
6140         /**
6141          * Class applied to buttons wrapping element
6142          * @property YAHOO.widget.CalendarNavigator.CLASSES.BUTTON
6143          * @type String
6144          * @static
6145          */
6146         BUTTON : "yui-cal-nav-btn",
6147         /**
6148          * Class applied to the validation error area's bounding box
6149          * @property YAHOO.widget.CalendarNavigator.CLASSES.ERROR
6150          * @type String
6151          * @static
6152          */
6153         ERROR : "yui-cal-nav-e",
6154         /**
6155          * Class applied to the year input control
6156          * @property YAHOO.widget.CalendarNavigator.CLASSES.YEAR_CTRL
6157          * @type String
6158          * @static
6159          */
6160         YEAR_CTRL : "yui-cal-nav-yc",
6161         /**
6162          * Class applied to the month input control
6163          * @property YAHOO.widget.CalendarNavigator.CLASSES.MONTH_CTRL
6164          * @type String
6165          * @static
6166          */
6167         MONTH_CTRL : "yui-cal-nav-mc",
6168         /**
6169          * Class applied to controls with invalid data (e.g. a year input field with invalid an year)
6170          * @property YAHOO.widget.CalendarNavigator.CLASSES.INVALID
6171          * @type String
6172          * @static
6173          */
6174         INVALID : "yui-invalid",
6175         /**
6176          * Class applied to default controls
6177          * @property YAHOO.widget.CalendarNavigator.CLASSES.DEFAULT
6178          * @type String
6179          * @static
6180          */
6181         DEFAULT : "yui-default"
6182     };
6183
6184     /**
6185      * Object literal containing the default configuration values for the CalendarNavigator
6186      * The configuration object is expected to follow the format below, with the properties being
6187      * case sensitive.
6188      * <dl>
6189      * <dt>strings</dt>
6190      * <dd><em>Object</em> :  An object with the properties shown below, defining the string labels to use in the Navigator's UI
6191      *     <dl>
6192      *         <dt>month</dt><dd><em>String</em> : The string to use for the month label. Defaults to "Month".</dd>
6193      *         <dt>year</dt><dd><em>String</em> : The string to use for the year label. Defaults to "Year".</dd>
6194      *         <dt>submit</dt><dd><em>String</em> : The string to use for the submit button label. Defaults to "Okay".</dd>
6195      *         <dt>cancel</dt><dd><em>String</em> : The string to use for the cancel button label. Defaults to "Cancel".</dd>
6196      *         <dt>invalidYear</dt><dd><em>String</em> : The string to use for invalid year values. Defaults to "Year needs to be a number".</dd>
6197      *     </dl>
6198      * </dd>
6199      * <dt>monthFormat</dt><dd><em>String</em> : The month format to use. Either YAHOO.widget.Calendar.LONG, or YAHOO.widget.Calendar.SHORT. Defaults to YAHOO.widget.Calendar.LONG</dd>
6200      * <dt>initialFocus</dt><dd><em>String</em> : Either "year" or "month" specifying which input control should get initial focus. Defaults to "year"</dd>
6201      * </dl>
6202      * @property DEFAULT_CONFIG
6203      * @type Object
6204      * @static
6205      */
6206     CN.DEFAULT_CONFIG = {
6207         strings : {
6208             month: "Month",
6209             year: "Year",
6210             submit: "Okay",
6211             cancel: "Cancel",
6212             invalidYear : "Year needs to be a number"
6213         },
6214         monthFormat: YAHOO.widget.Calendar.LONG,
6215         initialFocus: "year"
6216     };
6217     
6218     /**
6219      * Object literal containing the default configuration values for the CalendarNavigator
6220      * @property _DEFAULT_CFG
6221      * @protected
6222      * @deprecated Made public. See the public DEFAULT_CONFIG property
6223      * @type Object
6224      * @static
6225      */
6226     CN._DEFAULT_CFG = CN.DEFAULT_CONFIG;
6227
6228
6229     /**
6230      * The suffix added to the Calendar/CalendarGroup's ID, to generate
6231      * a unique ID for the Navigator and it's bounding box.
6232      * @property YAHOO.widget.CalendarNavigator.ID_SUFFIX
6233      * @static
6234      * @type String
6235      * @final
6236      */
6237     CN.ID_SUFFIX = "_nav";
6238     /**
6239      * The suffix added to the Navigator's ID, to generate
6240      * a unique ID for the month control.
6241      * @property YAHOO.widget.CalendarNavigator.MONTH_SUFFIX
6242      * @static
6243      * @type String 
6244      * @final
6245      */
6246     CN.MONTH_SUFFIX = "_month";
6247     /**
6248      * The suffix added to the Navigator's ID, to generate
6249      * a unique ID for the year control.
6250      * @property YAHOO.widget.CalendarNavigator.YEAR_SUFFIX
6251      * @static
6252      * @type String
6253      * @final
6254      */
6255     CN.YEAR_SUFFIX = "_year";
6256     /**
6257      * The suffix added to the Navigator's ID, to generate
6258      * a unique ID for the error bounding box.
6259      * @property YAHOO.widget.CalendarNavigator.ERROR_SUFFIX
6260      * @static
6261      * @type String
6262      * @final
6263      */
6264     CN.ERROR_SUFFIX = "_error";
6265     /**
6266      * The suffix added to the Navigator's ID, to generate
6267      * a unique ID for the "Cancel" button.
6268      * @property YAHOO.widget.CalendarNavigator.CANCEL_SUFFIX
6269      * @static
6270      * @type String
6271      * @final
6272      */
6273     CN.CANCEL_SUFFIX = "_cancel";
6274     /**
6275      * The suffix added to the Navigator's ID, to generate
6276      * a unique ID for the "Submit" button.
6277      * @property YAHOO.widget.CalendarNavigator.SUBMIT_SUFFIX
6278      * @static
6279      * @type String
6280      * @final
6281      */
6282     CN.SUBMIT_SUFFIX = "_submit";
6283
6284     /**
6285      * The number of digits to which the year input control is to be limited.
6286      * @property YAHOO.widget.CalendarNavigator.YR_MAX_DIGITS
6287      * @static
6288      * @type Number
6289      */
6290     CN.YR_MAX_DIGITS = 4;
6291
6292     /**
6293      * The amount by which to increment the current year value,
6294      * when the arrow up/down key is pressed on the year control
6295      * @property YAHOO.widget.CalendarNavigator.YR_MINOR_INC
6296      * @static
6297      * @type Number
6298      */
6299     CN.YR_MINOR_INC = 1;
6300
6301     /**
6302      * The amount by which to increment the current year value,
6303      * when the page up/down key is pressed on the year control
6304      * @property YAHOO.widget.CalendarNavigator.YR_MAJOR_INC
6305      * @static
6306      * @type Number
6307      */
6308     CN.YR_MAJOR_INC = 10;
6309
6310     /**
6311      * Artificial delay (in ms) between the time the Navigator is hidden
6312      * and the Calendar/CalendarGroup state is updated. Allows the user
6313      * the see the Calendar/CalendarGroup page changing. If set to 0
6314      * the Calendar/CalendarGroup page will be updated instantly
6315      * @property YAHOO.widget.CalendarNavigator.UPDATE_DELAY
6316      * @static
6317      * @type Number
6318      */
6319     CN.UPDATE_DELAY = 50;
6320
6321     /**
6322      * Regular expression used to validate the year input
6323      * @property YAHOO.widget.CalendarNavigator.YR_PATTERN
6324      * @static
6325      * @type RegExp
6326      */
6327     CN.YR_PATTERN = /^\d+$/;
6328     /**
6329      * Regular expression used to trim strings
6330      * @property YAHOO.widget.CalendarNavigator.TRIM
6331      * @static
6332      * @type RegExp
6333      */
6334     CN.TRIM = /^\s*(.*?)\s*$/;
6335 })();
6336
6337 YAHOO.widget.CalendarNavigator.prototype = {
6338
6339     /**
6340      * The unique ID for this CalendarNavigator instance
6341      * @property id
6342      * @type String
6343      */
6344     id : null,
6345
6346     /**
6347      * The Calendar/CalendarGroup instance to which the navigator belongs
6348      * @property cal
6349      * @type {Calendar|CalendarGroup}
6350      */
6351     cal : null,
6352
6353     /**
6354      * Reference to the HTMLElement used to render the navigator's bounding box
6355      * @property navEl
6356      * @type HTMLElement
6357      */
6358     navEl : null,
6359
6360     /**
6361      * Reference to the HTMLElement used to render the navigator's mask
6362      * @property maskEl
6363      * @type HTMLElement
6364      */
6365     maskEl : null,
6366
6367     /**
6368      * Reference to the HTMLElement used to input the year
6369      * @property yearEl
6370      * @type HTMLElement
6371      */
6372     yearEl : null,
6373
6374     /**
6375      * Reference to the HTMLElement used to input the month
6376      * @property monthEl
6377      * @type HTMLElement
6378      */
6379     monthEl : null,
6380
6381     /**
6382      * Reference to the HTMLElement used to display validation errors
6383      * @property errorEl
6384      * @type HTMLElement
6385      */
6386     errorEl : null,
6387
6388     /**
6389      * Reference to the HTMLElement used to update the Calendar/Calendar group
6390      * with the month/year values
6391      * @property submitEl
6392      * @type HTMLElement
6393      */
6394     submitEl : null,
6395     
6396     /**
6397      * Reference to the HTMLElement used to hide the navigator without updating the 
6398      * Calendar/Calendar group
6399      * @property cancelEl
6400      * @type HTMLElement
6401      */
6402     cancelEl : null,
6403
6404     /** 
6405      * Reference to the first focusable control in the navigator (by default monthEl)
6406      * @property firstCtrl
6407      * @type HTMLElement
6408      */
6409     firstCtrl : null,
6410     
6411     /** 
6412      * Reference to the last focusable control in the navigator (by default cancelEl)
6413      * @property lastCtrl
6414      * @type HTMLElement
6415      */
6416     lastCtrl : null,
6417
6418     /**
6419      * The document containing the Calendar/Calendar group instance
6420      * @protected
6421      * @property _doc
6422      * @type HTMLDocument
6423      */
6424     _doc : null,
6425
6426     /**
6427      * Internal state property for the current year displayed in the navigator
6428      * @protected
6429      * @property _year
6430      * @type Number
6431      */
6432     _year: null,
6433     
6434     /**
6435      * Internal state property for the current month index displayed in the navigator
6436      * @protected
6437      * @property _month
6438      * @type Number
6439      */
6440     _month: 0,
6441
6442     /**
6443      * Private internal state property which indicates whether or not the 
6444      * Navigator has been rendered.
6445      * @private
6446      * @property __rendered
6447      * @type Boolean
6448      */
6449     __rendered: false,
6450
6451     /**
6452      * Init lifecycle method called as part of construction
6453      * 
6454      * @method init
6455      * @param {Calendar} cal The instance of the Calendar or CalendarGroup to which this CalendarNavigator should be attached
6456      */
6457     init : function(cal) {
6458         var calBox = cal.oDomContainer;
6459
6460         this.cal = cal;
6461         this.id = calBox.id + YAHOO.widget.CalendarNavigator.ID_SUFFIX;
6462         this._doc = calBox.ownerDocument;
6463
6464         /**
6465          * Private flag, to identify IE Quirks
6466          * @private
6467          * @property __isIEQuirks
6468          */
6469         var ie = YAHOO.env.ua.ie;
6470         this.__isIEQuirks = (ie && ((ie <= 6) || (this._doc.compatMode == "BackCompat")));
6471     },
6472
6473     /**
6474      * Displays the navigator and mask, updating the input controls to reflect the 
6475      * currently set month and year. The show method will invoke the render method
6476      * if the navigator has not been renderered already, allowing for lazy rendering
6477      * of the control.
6478      * 
6479      * The show method will fire the Calendar/CalendarGroup's beforeShowNav and showNav events
6480      * 
6481      * @method show
6482      */
6483     show : function() {
6484         var CLASSES = YAHOO.widget.CalendarNavigator.CLASSES;
6485
6486         if (this.cal.beforeShowNavEvent.fire()) {
6487             if (!this.__rendered) {
6488                 this.render();
6489             }
6490             this.clearErrors();
6491
6492             this._updateMonthUI();
6493             this._updateYearUI();
6494             this._show(this.navEl, true);
6495
6496             this.setInitialFocus();
6497             this.showMask();
6498
6499             YAHOO.util.Dom.addClass(this.cal.oDomContainer, CLASSES.NAV_VISIBLE);
6500             this.cal.showNavEvent.fire();
6501         }
6502     },
6503
6504     /**
6505      * Hides the navigator and mask
6506      * 
6507      * The show method will fire the Calendar/CalendarGroup's beforeHideNav event and hideNav events
6508      * @method hide
6509      */
6510     hide : function() {
6511         var CLASSES = YAHOO.widget.CalendarNavigator.CLASSES;
6512
6513         if (this.cal.beforeHideNavEvent.fire()) {
6514             this._show(this.navEl, false);
6515             this.hideMask();
6516             YAHOO.util.Dom.removeClass(this.cal.oDomContainer, CLASSES.NAV_VISIBLE);
6517             this.cal.hideNavEvent.fire();
6518         }
6519     },
6520     
6521
6522     /**
6523      * Displays the navigator's mask element
6524      * 
6525      * @method showMask
6526      */
6527     showMask : function() {
6528         this._show(this.maskEl, true);
6529         if (this.__isIEQuirks) {
6530             this._syncMask();
6531         }
6532     },
6533
6534     /**
6535      * Hides the navigator's mask element
6536      * 
6537      * @method hideMask
6538      */
6539     hideMask : function() {
6540         this._show(this.maskEl, false);
6541     },
6542
6543     /**
6544      * Returns the current month set on the navigator
6545      * 
6546      * Note: This may not be the month set in the UI, if 
6547      * the UI contains an invalid value.
6548      * 
6549      * @method getMonth
6550      * @return {Number} The Navigator's current month index
6551      */
6552     getMonth: function() {
6553         return this._month;
6554     },
6555
6556     /**
6557      * Returns the current year set on the navigator
6558      * 
6559      * Note: This may not be the year set in the UI, if 
6560      * the UI contains an invalid value.
6561      * 
6562      * @method getYear
6563      * @return {Number} The Navigator's current year value
6564      */
6565     getYear: function() {
6566         return this._year;
6567     },
6568
6569     /**
6570      * Sets the current month on the Navigator, and updates the UI
6571      * 
6572      * @method setMonth
6573      * @param {Number} nMonth The month index, from 0 (Jan) through 11 (Dec).
6574      */
6575     setMonth : function(nMonth) {
6576         if (nMonth >= 0 && nMonth < 12) {
6577             this._month = nMonth;
6578         }
6579         this._updateMonthUI();
6580     },
6581
6582     /**
6583      * Sets the current year on the Navigator, and updates the UI. If the 
6584      * provided year is invalid, it will not be set.
6585      * 
6586      * @method setYear
6587      * @param {Number} nYear The full year value to set the Navigator to.
6588      */
6589     setYear : function(nYear) {
6590         var yrPattern = YAHOO.widget.CalendarNavigator.YR_PATTERN;
6591         if (YAHOO.lang.isNumber(nYear) && yrPattern.test(nYear+"")) {
6592             this._year = nYear;
6593         }
6594         this._updateYearUI();
6595     },
6596
6597     /**
6598      * Renders the HTML for the navigator, adding it to the 
6599      * document and attaches event listeners if it has not 
6600      * already been rendered.
6601      * 
6602      * @method render
6603      */
6604     render: function() {
6605         this.cal.beforeRenderNavEvent.fire();
6606         if (!this.__rendered) {
6607             this.createNav();
6608             this.createMask();
6609             this.applyListeners();
6610             this.__rendered = true;
6611         }
6612         this.cal.renderNavEvent.fire();
6613     },
6614
6615     /**
6616      * Creates the navigator's containing HTMLElement, it's contents, and appends 
6617      * the containg element to the Calendar/CalendarGroup's container.
6618      * 
6619      * @method createNav
6620      */
6621     createNav : function() {
6622         var NAV = YAHOO.widget.CalendarNavigator;
6623         var doc = this._doc;
6624
6625         var d = doc.createElement("div");
6626         d.className = NAV.CLASSES.NAV;
6627
6628         var htmlBuf = this.renderNavContents([]);
6629
6630         d.innerHTML = htmlBuf.join('');
6631         this.cal.oDomContainer.appendChild(d);
6632
6633         this.navEl = d;
6634
6635         this.yearEl = doc.getElementById(this.id + NAV.YEAR_SUFFIX);
6636         this.monthEl = doc.getElementById(this.id + NAV.MONTH_SUFFIX);
6637         this.errorEl = doc.getElementById(this.id + NAV.ERROR_SUFFIX);
6638         this.submitEl = doc.getElementById(this.id + NAV.SUBMIT_SUFFIX);
6639         this.cancelEl = doc.getElementById(this.id + NAV.CANCEL_SUFFIX);
6640
6641         if (YAHOO.env.ua.gecko && this.yearEl && this.yearEl.type == "text") {
6642             // Avoid XUL error on focus, select [ https://bugzilla.mozilla.org/show_bug.cgi?id=236791, 
6643             // supposedly fixed in 1.8.1, but there are reports of it still being around for methods other than blur ]
6644             this.yearEl.setAttribute("autocomplete", "off");
6645         }
6646
6647         this._setFirstLastElements();
6648     },
6649
6650     /**
6651      * Creates the Mask HTMLElement and appends it to the Calendar/CalendarGroups
6652      * container.
6653      * 
6654      * @method createMask
6655      */
6656     createMask : function() {
6657         var C = YAHOO.widget.CalendarNavigator.CLASSES;
6658
6659         var d = this._doc.createElement("div");
6660         d.className = C.MASK;
6661
6662         this.cal.oDomContainer.appendChild(d);
6663         this.maskEl = d;
6664     },
6665
6666     /**
6667      * Used to set the width/height of the mask in pixels to match the Calendar Container.
6668      * Currently only used for IE6 or IE in quirks mode. The other A-Grade browser are handled using CSS (width/height 100%).
6669      * <p>
6670      * The method is also registered as an HTMLElement resize listener on the Calendars container element.
6671      * </p>
6672      * @protected
6673      * @method _syncMask
6674      */
6675     _syncMask : function() {
6676         var c = this.cal.oDomContainer;
6677         if (c && this.maskEl) {
6678             var r = YAHOO.util.Dom.getRegion(c);
6679             YAHOO.util.Dom.setStyle(this.maskEl, "width", r.right - r.left + "px");
6680             YAHOO.util.Dom.setStyle(this.maskEl, "height", r.bottom - r.top + "px");
6681         }
6682     },
6683
6684     /**
6685      * Renders the contents of the navigator
6686      * 
6687      * @method renderNavContents
6688      * 
6689      * @param {Array} html The HTML buffer to append the HTML to.
6690      * @return {Array} A reference to the buffer passed in.
6691      */
6692     renderNavContents : function(html) {
6693         var NAV = YAHOO.widget.CalendarNavigator,
6694             C = NAV.CLASSES,
6695             h = html; // just to use a shorter name
6696
6697         h[h.length] = '<div class="' + C.MONTH + '">';
6698         this.renderMonth(h);
6699         h[h.length] = '</div>';
6700         h[h.length] = '<div class="' + C.YEAR + '">';
6701         this.renderYear(h);
6702         h[h.length] = '</div>';
6703         h[h.length] = '<div class="' + C.BUTTONS + '">';
6704         this.renderButtons(h);
6705         h[h.length] = '</div>';
6706         h[h.length] = '<div class="' + C.ERROR + '" id="' + this.id + NAV.ERROR_SUFFIX + '"></div>';
6707
6708         return h;
6709     },
6710
6711     /**
6712      * Renders the month label and control for the navigator
6713      * 
6714      * @method renderNavContents
6715      * @param {Array} html The HTML buffer to append the HTML to.
6716      * @return {Array} A reference to the buffer passed in.
6717      */
6718     renderMonth : function(html) {
6719         var NAV = YAHOO.widget.CalendarNavigator,
6720             C = NAV.CLASSES;
6721
6722         var id = this.id + NAV.MONTH_SUFFIX,
6723             mf = this.__getCfg("monthFormat"),
6724             months = this.cal.cfg.getProperty((mf == YAHOO.widget.Calendar.SHORT) ? "MONTHS_SHORT" : "MONTHS_LONG"),
6725             h = html;
6726
6727         if (months && months.length > 0) {
6728             h[h.length] = '<label for="' + id + '">';
6729             h[h.length] = this.__getCfg("month", true);
6730             h[h.length] = '</label>';
6731             h[h.length] = '<select name="' + id + '" id="' + id + '" class="' + C.MONTH_CTRL + '">';
6732             for (var i = 0; i < months.length; i++) {
6733                 h[h.length] = '<option value="' + i + '">';
6734                 h[h.length] = months[i];
6735                 h[h.length] = '</option>';
6736             }
6737             h[h.length] = '</select>';
6738         }
6739         return h;
6740     },
6741
6742     /**
6743      * Renders the year label and control for the navigator
6744      * 
6745      * @method renderYear
6746      * @param {Array} html The HTML buffer to append the HTML to.
6747      * @return {Array} A reference to the buffer passed in.
6748      */
6749     renderYear : function(html) {
6750         var NAV = YAHOO.widget.CalendarNavigator,
6751             C = NAV.CLASSES;
6752
6753         var id = this.id + NAV.YEAR_SUFFIX,
6754             size = NAV.YR_MAX_DIGITS,
6755             h = html;
6756
6757         h[h.length] = '<label for="' + id + '">';
6758         h[h.length] = this.__getCfg("year", true);
6759         h[h.length] = '</label>';
6760         h[h.length] = '<input type="text" name="' + id + '" id="' + id + '" class="' + C.YEAR_CTRL + '" maxlength="' + size + '"/>';
6761         return h;
6762     },
6763
6764     /**
6765      * Renders the submit/cancel buttons for the navigator
6766      * 
6767      * @method renderButton
6768      * @return {String} The HTML created for the Button UI
6769      */
6770     renderButtons : function(html) {
6771         var C = YAHOO.widget.CalendarNavigator.CLASSES;
6772         var h = html;
6773
6774         h[h.length] = '<span class="' + C.BUTTON + ' ' + C.DEFAULT + '">';
6775         h[h.length] = '<button type="button" id="' + this.id + '_submit' + '">';
6776         h[h.length] = this.__getCfg("submit", true);
6777         h[h.length] = '</button>';
6778         h[h.length] = '</span>';
6779         h[h.length] = '<span class="' + C.BUTTON +'">';
6780         h[h.length] = '<button type="button" id="' + this.id + '_cancel' + '">';
6781         h[h.length] = this.__getCfg("cancel", true);
6782         h[h.length] = '</button>';
6783         h[h.length] = '</span>';
6784
6785         return h;
6786     },
6787
6788     /**
6789      * Attaches DOM event listeners to the rendered elements
6790      * <p>
6791      * The method will call applyKeyListeners, to setup keyboard specific 
6792      * listeners
6793      * </p>
6794      * @method applyListeners
6795      */
6796     applyListeners : function() {
6797         var E = YAHOO.util.Event;
6798
6799         function yearUpdateHandler() {
6800             if (this.validate()) {
6801                 this.setYear(this._getYearFromUI());
6802             }
6803         }
6804
6805         function monthUpdateHandler() {
6806             this.setMonth(this._getMonthFromUI());
6807         }
6808
6809         E.on(this.submitEl, "click", this.submit, this, true);
6810         E.on(this.cancelEl, "click", this.cancel, this, true);
6811         E.on(this.yearEl, "blur", yearUpdateHandler, this, true);
6812         E.on(this.monthEl, "change", monthUpdateHandler, this, true);
6813
6814         if (this.__isIEQuirks) {
6815             YAHOO.util.Event.on(this.cal.oDomContainer, "resize", this._syncMask, this, true);
6816         }
6817
6818         this.applyKeyListeners();
6819     },
6820
6821     /**
6822      * Removes/purges DOM event listeners from the rendered elements
6823      * 
6824      * @method purgeListeners
6825      */
6826     purgeListeners : function() {
6827         var E = YAHOO.util.Event;
6828         E.removeListener(this.submitEl, "click", this.submit);
6829         E.removeListener(this.cancelEl, "click", this.cancel);
6830         E.removeListener(this.yearEl, "blur");
6831         E.removeListener(this.monthEl, "change");
6832         if (this.__isIEQuirks) {
6833             E.removeListener(this.cal.oDomContainer, "resize", this._syncMask);
6834         }
6835
6836         this.purgeKeyListeners();
6837     },
6838
6839     /**
6840      * Attaches DOM listeners for keyboard support. 
6841      * Tab/Shift-Tab looping, Enter Key Submit on Year element,
6842      * Up/Down/PgUp/PgDown year increment on Year element
6843      * <p>
6844      * NOTE: MacOSX Safari 2.x doesn't let you tab to buttons and 
6845      * MacOSX Gecko does not let you tab to buttons or select controls,
6846      * so for these browsers, Tab/Shift-Tab looping is limited to the 
6847      * elements which can be reached using the tab key.
6848      * </p>
6849      * @method applyKeyListeners
6850      */
6851     applyKeyListeners : function() {
6852         var E = YAHOO.util.Event,
6853             ua = YAHOO.env.ua;
6854
6855         // IE/Safari 3.1 doesn't fire keypress for arrow/pg keys (non-char keys)
6856         var arrowEvt = (ua.ie || ua.webkit) ? "keydown" : "keypress";
6857
6858         // - IE/Safari 3.1 doesn't fire keypress for non-char keys
6859         // - Opera doesn't allow us to cancel keydown or keypress for tab, but 
6860         //   changes focus successfully on keydown (keypress is too late to change focus - opera's already moved on).
6861         var tabEvt = (ua.ie || ua.opera || ua.webkit) ? "keydown" : "keypress";
6862
6863         // Everyone likes keypress for Enter (char keys) - whoo hoo!
6864         E.on(this.yearEl, "keypress", this._handleEnterKey, this, true);
6865
6866         E.on(this.yearEl, arrowEvt, this._handleDirectionKeys, this, true);
6867         E.on(this.lastCtrl, tabEvt, this._handleTabKey, this, true);
6868         E.on(this.firstCtrl, tabEvt, this._handleShiftTabKey, this, true);
6869     },
6870
6871     /**
6872      * Removes/purges DOM listeners for keyboard support
6873      *
6874      * @method purgeKeyListeners
6875      */
6876     purgeKeyListeners : function() {
6877         var E = YAHOO.util.Event,
6878             ua = YAHOO.env.ua;
6879
6880         var arrowEvt = (ua.ie || ua.webkit) ? "keydown" : "keypress";
6881         var tabEvt = (ua.ie || ua.opera || ua.webkit) ? "keydown" : "keypress";
6882
6883         E.removeListener(this.yearEl, "keypress", this._handleEnterKey);
6884         E.removeListener(this.yearEl, arrowEvt, this._handleDirectionKeys);
6885         E.removeListener(this.lastCtrl, tabEvt, this._handleTabKey);
6886         E.removeListener(this.firstCtrl, tabEvt, this._handleShiftTabKey);
6887     },
6888
6889     /**
6890      * Updates the Calendar/CalendarGroup's pagedate with the currently set month and year if valid.
6891      * <p>
6892      * If the currently set month/year is invalid, a validation error will be displayed and the 
6893      * Calendar/CalendarGroup's pagedate will not be updated.
6894      * </p>
6895      * @method submit
6896      */
6897     submit : function() {
6898         if (this.validate()) {
6899             this.hide();
6900
6901             this.setMonth(this._getMonthFromUI());
6902             this.setYear(this._getYearFromUI());
6903
6904             var cal = this.cal;
6905
6906             // Artificial delay, just to help the user see something changed
6907             var delay = YAHOO.widget.CalendarNavigator.UPDATE_DELAY;
6908             if (delay > 0) {
6909                 var nav = this;
6910                 window.setTimeout(function(){ nav._update(cal); }, delay);
6911             } else {
6912                 this._update(cal);
6913             }
6914         }
6915     },
6916
6917     /**
6918      * Updates the Calendar rendered state, based on the state of the CalendarNavigator
6919      * @method _update
6920      * @param cal The Calendar instance to update
6921      * @protected
6922      */
6923     _update : function(cal) {
6924         var date = YAHOO.widget.DateMath.getDate(this.getYear() - cal.cfg.getProperty("YEAR_OFFSET"), this.getMonth(), 1);
6925         cal.cfg.setProperty("pagedate", date);
6926         cal.render();
6927     },
6928
6929     /**
6930      * Hides the navigator and mask, without updating the Calendar/CalendarGroup's state
6931      * 
6932      * @method cancel
6933      */
6934     cancel : function() {
6935         this.hide();
6936     },
6937
6938     /**
6939      * Validates the current state of the UI controls
6940      * 
6941      * @method validate
6942      * @return {Boolean} true, if the current UI state contains valid values, false if not
6943      */
6944     validate : function() {
6945         if (this._getYearFromUI() !== null) {
6946             this.clearErrors();
6947             return true;
6948         } else {
6949             this.setYearError();
6950             this.setError(this.__getCfg("invalidYear", true));
6951             return false;
6952         }
6953     },
6954
6955     /**
6956      * Displays an error message in the Navigator's error panel
6957      * @method setError
6958      * @param {String} msg The error message to display
6959      */
6960     setError : function(msg) {
6961         if (this.errorEl) {
6962             this.errorEl.innerHTML = msg;
6963             this._show(this.errorEl, true);
6964         }
6965     },
6966
6967     /**
6968      * Clears the navigator's error message and hides the error panel
6969      * @method clearError 
6970      */
6971     clearError : function() {
6972         if (this.errorEl) {
6973             this.errorEl.innerHTML = "";
6974             this._show(this.errorEl, false);
6975         }
6976     },
6977
6978     /**
6979      * Displays the validation error UI for the year control
6980      * @method setYearError
6981      */
6982     setYearError : function() {
6983         YAHOO.util.Dom.addClass(this.yearEl, YAHOO.widget.CalendarNavigator.CLASSES.INVALID);
6984     },
6985
6986     /**
6987      * Removes the validation error UI for the year control
6988      * @method clearYearError
6989      */
6990     clearYearError : function() {
6991         YAHOO.util.Dom.removeClass(this.yearEl, YAHOO.widget.CalendarNavigator.CLASSES.INVALID);
6992     },
6993
6994     /**
6995      * Clears all validation and error messages in the UI
6996      * @method clearErrors
6997      */
6998     clearErrors : function() {
6999         this.clearError();
7000         this.clearYearError();
7001     },
7002
7003     /**
7004      * Sets the initial focus, based on the configured value
7005      * @method setInitialFocus
7006      */
7007     setInitialFocus : function() {
7008         var el = this.submitEl,
7009             f = this.__getCfg("initialFocus");
7010
7011         if (f && f.toLowerCase) {
7012             f = f.toLowerCase();
7013             if (f == "year") {
7014                 el = this.yearEl;
7015                 try {
7016                     this.yearEl.select();
7017                 } catch (selErr) {
7018                     // Ignore;
7019                 }
7020             } else if (f == "month") {
7021                 el = this.monthEl;
7022             }
7023         }
7024
7025         if (el && YAHOO.lang.isFunction(el.focus)) {
7026             try {
7027                 el.focus();
7028             } catch (focusErr) {
7029                 // TODO: Fall back if focus fails?
7030             }
7031         }
7032     },
7033
7034     /**
7035      * Removes all renderered HTML elements for the Navigator from
7036      * the DOM, purges event listeners and clears (nulls) any property
7037      * references to HTML references
7038      * @method erase
7039      */
7040     erase : function() {
7041         if (this.__rendered) {
7042             this.purgeListeners();
7043
7044             // Clear out innerHTML references
7045             this.yearEl = null;
7046             this.monthEl = null;
7047             this.errorEl = null;
7048             this.submitEl = null;
7049             this.cancelEl = null;
7050             this.firstCtrl = null;
7051             this.lastCtrl = null;
7052             if (this.navEl) {
7053                 this.navEl.innerHTML = "";
7054             }
7055
7056             var p = this.navEl.parentNode;
7057             if (p) {
7058                 p.removeChild(this.navEl);
7059             }
7060             this.navEl = null;
7061
7062             var pm = this.maskEl.parentNode;
7063             if (pm) {
7064                 pm.removeChild(this.maskEl);
7065             }
7066             this.maskEl = null;
7067             this.__rendered = false;
7068         }
7069     },
7070
7071     /**
7072      * Destroys the Navigator object and any HTML references
7073      * @method destroy
7074      */
7075     destroy : function() {
7076         this.erase();
7077         this._doc = null;
7078         this.cal = null;
7079         this.id = null;
7080     },
7081
7082     /**
7083      * Protected implementation to handle how UI elements are 
7084      * hidden/shown.
7085      *
7086      * @method _show
7087      * @protected
7088      */
7089     _show : function(el, bShow) {
7090         if (el) {
7091             YAHOO.util.Dom.setStyle(el, "display", (bShow) ? "block" : "none");
7092         }
7093     },
7094
7095     /**
7096      * Returns the month value (index), from the month UI element
7097      * @protected
7098      * @method _getMonthFromUI
7099      * @return {Number} The month index, or 0 if a UI element for the month
7100      * is not found
7101      */
7102     _getMonthFromUI : function() {
7103         if (this.monthEl) {
7104             return this.monthEl.selectedIndex;
7105         } else {
7106             return 0; // Default to Jan
7107         }
7108     },
7109
7110     /**
7111      * Returns the year value, from the Navitator's year UI element
7112      * @protected
7113      * @method _getYearFromUI
7114      * @return {Number} The year value set in the UI, if valid. null is returned if 
7115      * the UI does not contain a valid year value.
7116      */
7117     _getYearFromUI : function() {
7118         var NAV = YAHOO.widget.CalendarNavigator;
7119
7120         var yr = null;
7121         if (this.yearEl) {
7122             var value = this.yearEl.value;
7123             value = value.replace(NAV.TRIM, "$1");
7124
7125             if (NAV.YR_PATTERN.test(value)) {
7126                 yr = parseInt(value, 10);
7127             }
7128         }
7129         return yr;
7130     },
7131
7132     /**
7133      * Updates the Navigator's year UI, based on the year value set on the Navigator object
7134      * @protected
7135      * @method _updateYearUI
7136      */
7137     _updateYearUI : function() {
7138         if (this.yearEl && this._year !== null) {
7139             this.yearEl.value = this._year;
7140         }
7141     },
7142
7143     /**
7144      * Updates the Navigator's month UI, based on the month value set on the Navigator object
7145      * @protected
7146      * @method _updateMonthUI
7147      */
7148     _updateMonthUI : function() {
7149         if (this.monthEl) {
7150             this.monthEl.selectedIndex = this._month;
7151         }
7152     },
7153
7154     /**
7155      * Sets up references to the first and last focusable element in the Navigator's UI
7156      * in terms of tab order (Naviagator's firstEl and lastEl properties). The references
7157      * are used to control modality by looping around from the first to the last control
7158      * and visa versa for tab/shift-tab navigation.
7159      * <p>
7160      * See <a href="#applyKeyListeners">applyKeyListeners</a>
7161      * </p>
7162      * @protected
7163      * @method _setFirstLastElements
7164      */
7165     _setFirstLastElements : function() {
7166         this.firstCtrl = this.monthEl;
7167         this.lastCtrl = this.cancelEl;
7168
7169         // Special handling for MacOSX.
7170         // - Safari 2.x can't focus on buttons
7171         // - Gecko can't focus on select boxes or buttons
7172         if (this.__isMac) {
7173             if (YAHOO.env.ua.webkit && YAHOO.env.ua.webkit < 420){
7174                 this.firstCtrl = this.monthEl;
7175                 this.lastCtrl = this.yearEl;
7176             }
7177             if (YAHOO.env.ua.gecko) {
7178                 this.firstCtrl = this.yearEl;
7179                 this.lastCtrl = this.yearEl;
7180             }
7181         }
7182     },
7183
7184     /**
7185      * Default Keyboard event handler to capture Enter 
7186      * on the Navigator's year control (yearEl)
7187      * 
7188      * @method _handleEnterKey
7189      * @protected
7190      * @param {Event} e The DOM event being handled
7191      */
7192     _handleEnterKey : function(e) {
7193         var KEYS = YAHOO.util.KeyListener.KEY;
7194
7195         if (YAHOO.util.Event.getCharCode(e) == KEYS.ENTER) {
7196             YAHOO.util.Event.preventDefault(e);
7197             this.submit();
7198         }
7199     },
7200
7201     /**
7202      * Default Keyboard event handler to capture up/down/pgup/pgdown
7203      * on the Navigator's year control (yearEl).
7204      * 
7205      * @method _handleDirectionKeys
7206      * @protected
7207      * @param {Event} e The DOM event being handled
7208      */
7209     _handleDirectionKeys : function(e) {
7210         var E = YAHOO.util.Event,
7211             KEYS = YAHOO.util.KeyListener.KEY,
7212             NAV = YAHOO.widget.CalendarNavigator;
7213
7214         var value = (this.yearEl.value) ? parseInt(this.yearEl.value, 10) : null;
7215         if (isFinite(value)) {
7216             var dir = false;
7217             switch(E.getCharCode(e)) {
7218                 case KEYS.UP:
7219                     this.yearEl.value = value + NAV.YR_MINOR_INC;
7220                     dir = true;
7221                     break;
7222                 case KEYS.DOWN:
7223                     this.yearEl.value = Math.max(value - NAV.YR_MINOR_INC, 0);
7224                     dir = true;
7225                     break;
7226                 case KEYS.PAGE_UP:
7227                     this.yearEl.value = value + NAV.YR_MAJOR_INC;
7228                     dir = true;
7229                     break;
7230                 case KEYS.PAGE_DOWN:
7231                     this.yearEl.value = Math.max(value - NAV.YR_MAJOR_INC, 0);
7232                     dir = true;
7233                     break;
7234                 default:
7235                     break;
7236             }
7237             if (dir) {
7238                 E.preventDefault(e);
7239                 try {
7240                     this.yearEl.select();
7241                 } catch(err) {
7242                     // Ignore
7243                 }
7244             }
7245         }
7246     },
7247
7248     /**
7249      * Default Keyboard event handler to capture Tab 
7250      * on the last control (lastCtrl) in the Navigator.
7251      * 
7252      * @method _handleTabKey
7253      * @protected
7254      * @param {Event} e The DOM event being handled
7255      */
7256     _handleTabKey : function(e) {
7257         var E = YAHOO.util.Event,
7258             KEYS = YAHOO.util.KeyListener.KEY;
7259
7260         if (E.getCharCode(e) == KEYS.TAB && !e.shiftKey) {
7261             try {
7262                 E.preventDefault(e);
7263                 this.firstCtrl.focus();
7264             } catch (err) {
7265                 // Ignore - mainly for focus edge cases
7266             }
7267         }
7268     },
7269
7270     /**
7271      * Default Keyboard event handler to capture Shift-Tab 
7272      * on the first control (firstCtrl) in the Navigator.
7273      * 
7274      * @method _handleShiftTabKey
7275      * @protected
7276      * @param {Event} e The DOM event being handled
7277      */
7278     _handleShiftTabKey : function(e) {
7279         var E = YAHOO.util.Event,
7280             KEYS = YAHOO.util.KeyListener.KEY;
7281
7282         if (e.shiftKey && E.getCharCode(e) == KEYS.TAB) {
7283             try {
7284                 E.preventDefault(e);
7285                 this.lastCtrl.focus();
7286             } catch (err) {
7287                 // Ignore - mainly for focus edge cases
7288             }
7289         }
7290     },
7291
7292     /**
7293      * Retrieve Navigator configuration values from 
7294      * the parent Calendar/CalendarGroup's config value.
7295      * <p>
7296      * If it has not been set in the user provided configuration, the method will 
7297      * return the default value of the configuration property, as set in DEFAULT_CONFIG
7298      * </p>
7299      * @private
7300      * @method __getCfg
7301      * @param {String} Case sensitive property name.
7302      * @param {Boolean} true, if the property is a string property, false if not.
7303      * @return The value of the configuration property
7304      */
7305     __getCfg : function(prop, bIsStr) {
7306         var DEF_CFG = YAHOO.widget.CalendarNavigator.DEFAULT_CONFIG;
7307         var cfg = this.cal.cfg.getProperty("navigator");
7308
7309         if (bIsStr) {
7310             return (cfg !== true && cfg.strings && cfg.strings[prop]) ? cfg.strings[prop] : DEF_CFG.strings[prop];
7311         } else {
7312             return (cfg !== true && cfg[prop]) ? cfg[prop] : DEF_CFG[prop];
7313         }
7314     },
7315
7316     /**
7317      * Private flag, to identify MacOS
7318      * @private
7319      * @property __isMac
7320      */
7321     __isMac : (navigator.userAgent.toLowerCase().indexOf("macintosh") != -1)
7322
7323 };
7324 YAHOO.register("calendar", YAHOO.widget.Calendar, {version: "2.8.0r4", build: "2449"});