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