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