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