[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/2in3/2.9.0/build/yui2-calendar/ -> yui2-calendar-debug.js (source)

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1