1/* http://keith-wood.name/datepick.html 2 Date picker for jQuery v4.0.4. 3 Written by Keith Wood (kbwood{at}iinet.com.au) February 2010. 4 Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and 5 MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses. 6 Please attribute the author if you use it. */ 7 8(function($) { // Hide scope, no $ conflict 9 10/* Datepicker manager. */ 11function Datepicker() { 12 this._defaults = { 13 pickerClass: '', // CSS class to add to this instance of the datepicker 14 showOnFocus: true, // True for popup on focus, false for not 15 showTrigger: null, // Element to be cloned for a trigger, null for none 16 showAnim: 'show', // Name of jQuery animation for popup, '' for no animation 17 showOptions: {}, // Options for enhanced animations 18 showSpeed: 'normal', // Duration of display/closure 19 popupContainer: null, // The element to which a popup calendar is added, null for body 20 alignment: 'bottom', // Alignment of popup - with nominated corner of input: 21 // 'top' or 'bottom' aligns depending on language direction, 22 // 'topLeft', 'topRight', 'bottomLeft', 'bottomRight' 23 fixedWeeks: false, // True to always show 6 weeks, false to only show as many as are needed 24 firstDay: 0, // First day of the week, 0 = Sunday, 1 = Monday, ... 25 calculateWeek: this.iso8601Week, // Calculate week of the year from a date, null for ISO8601 26 monthsToShow: 1, // How many months to show, cols or [rows, cols] 27 monthsOffset: 0, // How many months to offset the primary month by 28 monthsToStep: 1, // How many months to move when prev/next clicked 29 monthsToJump: 12, // How many months to move when large prev/next clicked 30 useMouseWheel: true, // True to use mousewheel if available, false to never use it 31 changeMonth: true, // True to change month/year via drop-down, false for navigation only 32 yearRange: 'c-10:c+10', // Range of years to show in drop-down: 'any' for direct text entry 33 // or 'start:end', where start/end are '+-nn' for relative to today 34 // or 'c+-nn' for relative to the currently selected date 35 // or 'nnnn' for an absolute year 36 shortYearCutoff: '+10', // Cutoff for two-digit year in the current century 37 showOtherMonths: false, // True to show dates from other months, false to not show them 38 selectOtherMonths: false, // True to allow selection of dates from other months too 39 defaultDate: null, // Date to show if no other selected 40 selectDefaultDate: false, // True to pre-select the default date if no other is chosen 41 minDate: null, // The minimum selectable date 42 maxDate: null, // The maximum selectable date 43 dateFormat: 'mm/dd/yyyy', // Format for dates 44 autoSize: false, // True to size the input field according to the date format 45 rangeSelect: false, // Allows for selecting a date range on one date picker 46 rangeSeparator: ' - ', // Text between two dates in a range 47 multiSelect: 0, // Maximum number of selectable dates, zero for single select 48 multiSeparator: ',', // Text between multiple dates 49 onDate: null, // Callback as a date is added to the datepicker 50 onShow: null, // Callback just before a datepicker is shown 51 onChangeMonthYear: null, // Callback when a new month/year is selected 52 onSelect: null, // Callback when a date is selected 53 onClose: null, // Callback when a datepicker is closed 54 altField: null, // Alternate field to update in synch with the datepicker 55 altFormat: null, // Date format for alternate field, defaults to dateFormat 56 constrainInput: true, // True to constrain typed input to dateFormat allowed characters 57 commandsAsDateFormat: false, // True to apply formatDate to the command texts 58 commands: this.commands // Command actions that may be added to a layout by name 59 }; 60 this.regional = { 61 '': { // US/English 62 monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 63 'July', 'August', 'September', 'October', 'November', 'December'], 64 monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], 65 dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], 66 dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], 67 dayNamesMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], 68 dateFormat: 'mm/dd/yyyy', // See options on formatDate 69 firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ... 70 renderer: this.defaultRenderer, // The rendering templates 71 prevText: '< Prev', // Text for the previous month command 72 prevStatus: 'Show the previous month', // Status text for the previous month command 73 prevJumpText: '<<', // Text for the previous year command 74 prevJumpStatus: 'Show the previous year', // Status text for the previous year command 75 nextText: 'Next >', // Text for the next month command 76 nextStatus: 'Show the next month', // Status text for the next month command 77 nextJumpText: '>>', // Text for the next year command 78 nextJumpStatus: 'Show the next year', // Status text for the next year command 79 currentText: 'Current', // Text for the current month command 80 currentStatus: 'Show the current month', // Status text for the current month command 81 todayText: 'Today', // Text for the today's month command 82 todayStatus: 'Show today\'s month', // Status text for the today's month command 83 clearText: 'Clear', // Text for the clear command 84 clearStatus: 'Clear all the dates', // Status text for the clear command 85 closeText: 'Close', // Text for the close command 86 closeStatus: 'Close the datepicker', // Status text for the close command 87 yearStatus: 'Change the year', // Status text for year selection 88 monthStatus: 'Change the month', // Status text for month selection 89 weekText: 'Wk', // Text for week of the year column header 90 weekStatus: 'Week of the year', // Status text for week of the year column header 91 dayStatus: 'Select DD, M d, yyyy', // Status text for selectable days 92 defaultStatus: 'Select a date', // Status text shown by default 93 isRTL: false // True if language is right-to-left 94 }}; 95 $.extend(this._defaults, this.regional['']); 96 this._disabled = []; 97} 98 99$.extend(Datepicker.prototype, { 100 dataName: 'datepick', 101 102 /* Class name added to elements to indicate already configured with datepicker. */ 103 markerClass: 'hasDatepick', 104 105 _popupClass: 'datepick-popup', // Marker for popup division 106 _triggerClass: 'datepick-trigger', // Marker for trigger element 107 _disableClass: 'datepick-disable', // Marker for disabled element 108 _coverClass: 'datepick-cover', // Marker for iframe backing element 109 _monthYearClass: 'datepick-month-year', // Marker for month/year inputs 110 _curMonthClass: 'datepick-month-', // Marker for current month/year 111 _anyYearClass: 'datepick-any-year', // Marker for year direct input
112 _curDoWClass: 'datepick-dow-', // Marker for day of week 113 114 commands: { // Command actions that may be added to a layout by name 115 // name: { // The command name, use '{button:name}' or '{link:name}' in layouts 116 // text: '', // The field in the regional settings for the displayed text 117 // status: '', // The field in the regional settings for the status text 118 // // The keystroke to trigger the action 119 // keystroke: {keyCode: nn, ctrlKey: boolean, altKey: boolean, shiftKey: boolean}, 120 // enabled: fn, // The function that indicates the command is enabled 121 // date: fn, // The function to get the date associated with this action 122 // action: fn} // The function that implements the action 123 prev: {text: 'prevText', status: 'prevStatus', // Previous month 124 keystroke: {keyCode: 33}, // Page up 125 enabled: function(inst) { 126 var minDate = inst.curMinDate(); 127 return (!minDate || $.datepick.add($.datepick.day( 128 $.datepick.add($.datepick.newDate(inst.drawDate), 129 1 - inst.get('monthsToStep') - inst.get('monthsOffset'), 'm'), 1), -1, 'd'). 130 getTime() >= minDate.getTime()); }, 131 date: function(inst) { 132 return $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate), 133 -inst.get('monthsToStep') - inst.get('monthsOffset'), 'm'), 1); }, 134 action: function(inst) { 135 $.datepick.changeMonth(this, -inst.get('monthsToStep')); } 136 }, 137 prevJump: {text: 'prevJumpText', status: 'prevJumpStatus', // Previous year 138 keystroke: {keyCode: 33, ctrlKey: true}, // Ctrl + Page up 139 enabled: function(inst) { 140 var minDate = inst.curMinDate(); 141 return (!minDate || $.datepick.add($.datepick.day( 142 $.datepick.add($.datepick.newDate(inst.drawDate), 143 1 - inst.get('monthsToJump') - inst.get('monthsOffset'), 'm'), 1), -1, 'd'). 144 getTime() >= minDate.getTime()); }, 145 date: function(inst) { 146 return $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate), 147 -inst.get('monthsToJump') - inst.get('monthsOffset'), 'm'), 1); }, 148 action: function(inst) { 149 $.datepick.changeMonth(this, -inst.get('monthsToJump')); } 150 }, 151 next: {text: 'nextText', status: 'nextStatus', // Next month 152 keystroke: {keyCode: 34}, // Page down 153 enabled: function(inst) { 154 var maxDate = inst.get('maxDate'); 155 return (!maxDate || $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate), 156 inst.get('monthsToStep') - inst.get('monthsOffset'), 'm'), 1). 157 getTime() <= maxDate.getTime()); }, 158 date: function(inst) { 159 return $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate), 160 inst.get('monthsToStep') - inst.get('monthsOffset'), 'm'), 1); }, 161 action: function(inst) { 162 $.datepick.changeMonth(this, inst.get('monthsToStep')); } 163 }, 164 nextJump: {text: 'nextJumpText', status: 'nextJumpStatus', // Next year 165 keystroke: {keyCode: 34, ctrlKey: true}, // Ctrl + Page down 166 enabled: function(inst) { 167 var maxDate = inst.get('maxDate'); 168 return (!maxDate || $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate), 169 inst.get('monthsToJump') - inst.get('monthsOffset'), 'm'), 1). 170 getTime() <= maxDate.getTime()); }, 171 date: function(inst) { 172 return $.datepick.day($.datepick.add($.datepick.newDate(inst.drawDate), 173 inst.get('monthsToJump') - inst.get('monthsOffset'), 'm'), 1); }, 174 action: function(inst) { 175 $.datepick.changeMonth(this, inst.get('monthsToJump')); } 176 }, 177 current: {text: 'currentText', status: 'currentStatus', // Current month 178 keystroke: {keyCode: 36, ctrlKey: true}, // Ctrl + Home 179 enabled: function(inst) { 180 var minDate = inst.curMinDate(); 181 var maxDate = inst.get('maxDate'); 182 var curDate = inst.selectedDates[0] || $.datepick.today(); 183 return (!minDate || curDate.getTime() >= minDate.getTime()) && 184 (!maxDate || curDate.getTime() <= maxDate.getTime()); }, 185 date: function(inst) { 186 return inst.selectedDates[0] || $.datepick.today(); }, 187 action: function(inst) { 188 var curDate = inst.selectedDates[0] || $.datepick.today(); 189 $.datepick.showMonth(this, curDate.getFullYear(), curDate.getMonth() + 1); } 190 }, 191 today: {text: 'todayText', status: 'todayStatus', // Today's month 192 keystroke: {keyCode: 36, ctrlKey: true}, // Ctrl + Home 193 enabled: function(inst) { 194 var minDate = inst.curMinDate(); 195 var maxDate = inst.get('maxDate'); 196 return (!minDate || $.datepick.today().getTime() >= minDate.getTime()) && 197 (!maxDate || $.datepick.today().getTime() <= maxDate.getTime()); }, 198 date: function(inst) { return $.datepick.today(); }, 199 action: function(inst) { $.datepick.showMonth(this); } 200 }, 201 clear: {text: 'clearText', status: 'clearStatus', // Clear the datepicker 202 keystroke: {keyCode: 35, ctrlKey: true}, // Ctrl + End 203 enabled: function(inst) { return true; }, 204 date: function(inst) { return null; }, 205 action: function(inst) { $.datepick.clear(this); } 206 }, 207 close: {text: 'closeText', status: 'closeStatus', // Close the datepicker 208 keystroke: {keyCode: 27}, // Escape 209 enabled: function(inst) { return true; }, 210 date: function(inst) { return null; }, 211 action: function(inst) { $.datepick.hide(this); } 212 }, 213 prevWeek: {text: 'prevWeekText', status: 'prevWeekStatus', // Previous week 214 keystroke: {keyCode: 38, ctrlKey: true}, // Ctrl + Up 215 enabled: function(inst) { 216 var minDate = inst.curMinDate(); 217 return (!minDate || $.datepick.add($.datepick.newDate(inst.drawDate), -7, 'd'). 218 getTime() >= minDate.getTime()); }, 219 date: function(inst) { return $.datepick.add($.datepick.newDate(inst.drawDate), -7, 'd'); }, 220 action: function(inst) { $.datepick.changeDay(this, -7); } 221 }, 222 prevDay: {text: 'prevDayText', status: 'prevDayStatus', // Previous day 223 keystroke: {keyCode: 37, ctrlKey: true}, // Ctrl + Left 224 enabled: function(inst) { 225 var minDate = inst.curMinDate(); 226 return (!minDate || $.datepick.add($.datepick.newDate(inst.drawDate), -1, 'd'). 227 getTime() >= minDate.getTime()); }, 228 date: function(inst) { return $.datepick.add($.datepick.newDate(inst.drawDate), -1, 'd'); }, 229 action: function(inst) { $.datepick.changeDay(this, -1); } 230 }, 231 nextDay: {text: 'nextDayText', status: 'nextDayStatus', // Next day 232 keystroke: {keyCode: 39, ctrlKey: true}, // Ctrl + Right 233 enabled: function(inst) { 234 var maxDate = inst.get('maxDate'); 235 return (!maxDate || $.datepick.add($.datepick.newDate(inst.drawDate), 1, 'd'). 236 getTime() <= maxDate.getTime()); }, 237 date: function(inst) { return $.datepick.add($.datepick.newDate(inst.drawDate), 1, 'd'); }, 238 action: function(inst) { $.datepick.changeDay(this, 1); } 239 }, 240 nextWeek: {text: 'nextWeekText', status: 'nextWeekStatus', // Next week 241 keystroke: {keyCode: 40, ctrlKey: true}, // Ctrl + Down 242 enabled: function(inst) { 243 var maxDate = inst.get('maxDate'); 244 return (!maxDate || $.datepick.add($.datepick.newDate(inst.drawDate), 7, 'd'). 245 getTime() <= maxDate.getTime()); }, 246 date: function(inst) { return $.datepick.add($.datepick.newDate(inst.drawDate), 7, 'd'); }, 247 action: function(inst) { $.datepick.changeDay(this, 7); } 248 } 249 }, 250 251 /* Default template for generating a datepicker. */ 252 defaultRenderer: { 253 // Anywhere: '{l10n:name}' to insert localised value for name, 254 // '{link:name}' to insert a link trigger for command name, 255 // '{button:name}' to insert a button trigger for command name, 256 // '{popup:start}...{popup:end}' to mark a section for inclusion in a popup datepicker only, 257 // '{inline:start}...{inline:end}' to mark a section for inclusion in an inline datepicker only 258 // Overall structure: '{months}' to insert calendar months 259 picker: '<div class="datepick">' + 260 '<div class="datepick-nav">{link:prev}{link:today}{link:next}</div>{months}' + 261 '{popup:start}<div class="datepick-ctrl">{link:clear}{link:close}</div>{popup:end}' + 262 '<div class="datepick-clear-fix"></div></div>', 263 // One row of months: '{months}' to insert calendar months 264 monthRow: '<div class="datepick-month-row">{months}</div>', 265 // A single month: '{monthHeader:dateFormat}' to insert the month header - 266 // dateFormat is optional and defaults to 'MM yyyy', 267 // '{weekHeader}' to insert a week header, '{weeks}' to insert the month's weeks 268 month: '<div class="datepick-month"><div class="datepick-month-header">{monthHeader}</div>' + 269 '<table><thead>{weekHeader}</thead><tbody>{weeks}</tbody></table></div>', 270 // A week header: '{days}' to insert individual day names 271 weekHeader: '<tr>{days}</tr>', 272 // Individual day header: '{day}' to insert day name 273 dayHeader: '<th>{day}</th>', 274 // One week of the month: '{days}' to insert the week's days, '{weekOfYear}' to insert week of year 275 week: '<tr>{days}</tr>', 276 // An individual day: '{day}' to insert day value 277 day: '<td>{day}</td>', 278 // jQuery selector, relative to picker, for a single month 279 monthSelector: '.datepick-month', 280 // jQuery selector, relative to picker, for individual days 281 daySelector: 'td', 282 // Class for right-to-left (RTL) languages 283 rtlClass: 'datepick-rtl', 284 // Class for multi-month datepickers 285 multiClass: 'datepick-multi', 286 // Class for selectable dates 287 defaultClass: '', 288 // Class for currently selected dates 289 selectedClass: 'datepick-selected', 290 // Class for highlighted dates 291 highlightedClass: 'datepick-highlight', 292 // Class for today 293 todayClass: 'datepick-today', 294 // Class for days from other months 295 otherMonthClass: 'datepick-other-month', 296 // Class for days on weekends 297 weekendClass: 'datepick-weekend', 298 // Class prefix for commands 299 commandClass: 'datepick-cmd', 300 // Extra class(es) for commands that are buttons
301 commandButtonClass: '', 302 // Extra class(es) for commands that are links 303 commandLinkClass: '', 304 // Class for disabled commands 305 disabledClass: 'datepick-disabled' 306 }, 307 308 /* Override the default settings for all datepicker instances. 309 @param settings (object) the new settings to use as defaults 310 @return (Datepicker) this object */ 311 setDefaults: function(settings) { 312 $.extend(this._defaults, settings || {}); 313 return this; 314 }, 315 316 _ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) + 317 Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000), 318 _msPerDay: 24 * 60 * 60 * 1000, 319 320 ATOM: 'yyyy-mm-dd', // RFC 3339/ISO 8601 321 COOKIE: 'D, dd M yyyy', 322 FULL: 'DD, MM d, yyyy', 323 ISO_8601: 'yyyy-mm-dd', 324 JULIAN: 'J', 325 RFC_822: 'D, d M yy', 326 RFC_850: 'DD, dd-M-yy', 327 RFC_1036: 'D, d M yy', 328 RFC_1123: 'D, d M yyyy', 329 RFC_2822: 'D, d M yyyy', 330 RSS: 'D, d M yy', // RFC 822 331 TICKS: '!', 332 TIMESTAMP: '@', 333 W3C: 'yyyy-mm-dd', // ISO 8601 334 335 /* Format a date object into a string value. 336 The format can be combinations of the following: 337 d - day of month (no leading zero) 338 dd - day of month (two digit) 339 o - day of year (no leading zeros) 340 oo - day of year (three digit) 341 D - day name short 342 DD - day name long 343 w - week of year (no leading zero) 344 ww - week of year (two digit) 345 m - month of year (no leading zero) 346 mm - month of year (two digit) 347 M - month name short 348 MM - month name long 349 yy - year (two digit) 350 yyyy - year (four digit) 351 @ - Unix timestamp (s since 01/01/1970) 352 ! - Windows ticks (100ns since 01/01/0001) 353 '...' - literal text 354 '' - single quote 355 @param format (string) the desired format of the date (optional, default datepicker format) 356 @param date (Date) the date value to format 357 @param settings (object) attributes include: 358 dayNamesShort (string[]) abbreviated names of the days from Sunday (optional) 359 dayNames (string[]) names of the days from Sunday (optional) 360 monthNamesShort (string[]) abbreviated names of the months (optional) 361 monthNames (string[]) names of the months (optional) 362 calculateWeek (function) function that determines week of the year (optional) 363 @return (string) the date in the above format */ 364 formatDate: function(format, date, settings) { 365 if (typeof format != 'string') { 366 settings = date; 367 date = format; 368 format = ''; 369 } 370 if (!date) { 371 return ''; 372 } 373 format = format || this._defaults.dateFormat; 374 settings = settings || {}; 375 var dayNamesShort = settings.dayNamesShort || this._defaults.dayNamesShort; 376 var dayNames = settings.dayNames || this._defaults.dayNames; 377 var monthNamesShort = settings.monthNamesShort || this._defaults.monthNamesShort; 378 var monthNames = settings.monthNames || this._defaults.monthNames; 379 var calculateWeek = settings.calculateWeek || this._defaults.calculateWeek; 380 // Check whether a format character is doubled 381 var doubled = function(match, step) { 382 var matches = 1;
383 while (iFormat + matches < format.length && format.charAt(iFormat + matches) == match) { 384 matches++; 385 } 386 iFormat += matches - 1; 387 return Math.floor(matches / (step || 1)) > 1; 388 }; 389 // Format a number, with leading zeroes if necessary 390 var formatNumber = function(match, value, len, step) { 391 var num = '' + value; 392 if (doubled(match, step)) { 393 while (num.length < len) { 394 num = '0' + num; 395 } 396 } 397 return num; 398 }; 399 // Format a name, short or long as requested 400 var formatName = function(match, value, shortNames, longNames) { 401 return (doubled(match) ? longNames[value] : shortNames[value]); 402 }; 403 var output = ''; 404 var literal = false; 405 for (var iFormat = 0; iFormat < format.length; iFormat++) { 406 if (literal) { 407 if (format.charAt(iFormat) == "'" && !doubled("'")) { 408 literal = false; 409 } 410 else { 411 output += format.charAt(iFormat); 412 } 413 } 414 else { 415 switch (format.charAt(iFormat)) { 416 case 'd': output += formatNumber('d', date.getDate(), 2); break; 417 case 'D': output += formatName('D', date.getDay(), 418 dayNamesShort, dayNames); break; 419 case 'o': output += formatNumber('o', this.dayOfYear(date), 3); break; 420 case 'w': output += formatNumber('w', calculateWeek(date), 2); break; 421 case 'm': output += formatNumber('m', date.getMonth() + 1, 2); break; 422 case 'M': output += formatName('M', date.getMonth(), 423 monthNamesShort, monthNames); break; 424 case 'y': 425 output += (doubled('y', 2) ? date.getFullYear() : 426 (date.getFullYear() % 100 < 10 ? '0' : '') + date.getFullYear() % 100); 427 break; 428 case '@': output += Math.floor(date.getTime() / 1000); break; 429 case '!': output += date.getTime() * 10000 + this._ticksTo1970; break; 430 case "'": 431 if (doubled("'")) { 432 output += "'"; 433 } 434 else { 435 literal = true; 436 } 437 break; 438 default: 439 output += format.charAt(iFormat); 440 } 441 } 442 } 443 return output; 444 }, 445 446 /* Parse a string value into a date object. 447 See formatDate for the possible formats, plus: 448 * - ignore rest of string 449 @param format (string) the expected format of the date ('' for default datepicker format) 450 @param value (string) the date in the above format 451 @param settings (object) attributes include: 452 shortYearCutoff (number) the cutoff year for determining the century (optional) 453 dayNamesShort (string[]) abbreviated names of the days from Sunday (optional) 454 dayNames (string[]) names of the days from Sunday (optional) 455 monthNamesShort (string[]) abbreviated names of the months (optional) 456 monthNames (string[]) names of the months (optional) 457 @return (Date) the extracted date value or null if value is blank 458 @throws errors if the format and/or value are missing, 459 if the value doesn't match the format, 460 or if the date is invalid */ 461 parseDate: function(format, value, settings) { 462 if (value == null) { 463 throw 'Invalid arguments'; 464 } 465 value = (typeof value == 'object' ? value.toString() : value + ''); 466 if (value == '') { 467 return null; 468 } 469 format = format || this._defaults.dateFormat; 470 settings = settings || {}; 471 var shortYearCutoff = settings.shortYearCutoff || this._defaults.shortYearCutoff; 472 shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff : 473 this.today().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); 474 var dayNamesShort = settings.dayNamesShort || this._defaults.dayNamesShort; 475 var dayNames = settings.dayNames || this._defaults.dayNames; 476 var monthNamesShort = settings.monthNamesShort || this._defaults.monthNamesShort; 477 var monthNames = settings.monthNames || this._defaults.monthNames; 478 var year = -1; 479 var month = -1; 480 var day = -1; 481 var doy = -1; 482 var shortYear = false; 483 var literal = false; 484 // Check whether a format character is doubled 485 var doubled = function(match, step) { 486 var matches = 1;
487 while (iFormat + matches < format.length && format.charAt(iFormat + matches) == match) { 488 matches++; 489 } 490 iFormat += matches - 1; 491 return Math.floor(matches / (step || 1)) > 1; 492 }; 493 // Extract a number from the string value 494 var getNumber = function(match, step) { 495 doubled(match, step); 496 var size = [2, 3, 4, 11, 20]['oy@!'.indexOf(match) + 1]; 497 var digits = new RegExp('^-?\\d{1,' + size + '}'); 498 var num = value.substring(iValue).match(digits); 499 if (!num) { 500 throw 'Missing number at position {0}'.replace(/\{0\}/, iValue); 501 } 502 iValue += num[0].length; 503 return parseInt(num[0], 10); 504 }; 505 // Extract a name from the string value and convert to an index 506 var getName = function(match, shortNames, longNames, step) { 507 var names = (doubled(match, step) ? longNames : shortNames); 508 for (var i = 0; i < names.length; i++) { 509 if (value.substr(iValue, names[i].length) == names[i]) { 510 iValue += names[i].length; 511 return i + 1; 512 } 513 } 514 throw 'Unknown name at position {0}'.replace(/\{0\}/, iValue); 515 }; 516 // Confirm that a literal character matches the string value 517 var checkLiteral = function() { 518 if (value.charAt(iValue) != format.charAt(iFormat)) { 519 throw 'Unexpected literal at position {0}'.replace(/\{0\}/, iValue); 520 } 521 iValue++; 522 }; 523 var iValue = 0; 524 for (var iFormat = 0; iFormat < format.length; iFormat++) { 525 if (literal) { 526 if (format.charAt(iFormat) == "'" && !doubled("'")) { 527 literal = false; 528 } 529 else { 530 checkLiteral(); 531 } 532 } 533 else { 534 switch (format.charAt(iFormat)) { 535 case 'd': day = getNumber('d'); break; 536 case 'D': getName('D', dayNamesShort, dayNames); break; 537 case 'o': doy = getNumber('o'); break; 538 case 'w': getNumber('w'); break; 539 case 'm': month = getNumber('m'); break; 540 case 'M': month = getName('M', monthNamesShort, monthNames); break; 541 case 'y': 542 var iSave = iFormat; 543 shortYear = !doubled('y', 2); 544 iFormat = iSave; 545 year = getNumber('y', 2); 546 break; 547 case '@': 548 var date = this._normaliseDate(new Date(getNumber('@') * 1000)); 549 year = date.getFullYear(); 550 month = date.getMonth() + 1; 551 day = date.getDate(); 552 break; 553 case '!': 554 var date = this._normaliseDate( 555 new Date((getNumber('!') - this._ticksTo1970) / 10000)); 556 year = date.getFullYear(); 557 month = date.getMonth() + 1; 558 day = date.getDate(); 559 break; 560 case '*': iValue = value.length; break; 561 case "'": 562 if (doubled("'")) { 563 checkLiteral(); 564 } 565 else { 566 literal = true; 567 } 568 break; 569 default: checkLiteral(); 570 } 571 } 572 } 573 if (iValue < value.length) { 574 throw 'Additional text found at end'; 575 } 576 if (year == -1) { 577 year = this.today().getFullYear(); 578 } 579 else if (year < 100 && shortYear) { 580 year += (shortYearCutoff == -1 ? 1900 : this.today().getFullYear() - 581 this.today().getFullYear() % 100 - (year <= shortYearCutoff ? 0 : 100)); 582 } 583 if (doy > -1) { 584 month = 1; 585 day = doy; 586 for (var dim = this.daysInMonth(year, month); day > dim; 587 dim = this.daysInMonth(year, month)) { 588 month++; 589 day -= dim; 590 } 591 } 592 var date = this.newDate(year, month, day); 593 if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != day) { 594 throw 'Invalid date'; 595 } 596 return date; 597 }, 598 599 /* A date may be specified as an exact value or a relative one. 600 @param dateSpec (Date or number or string) the date as an object or string 601 in the given format or an offset - numeric days from today, 602 or string amounts and periods, e.g. '+1m +2w' 603 @param defaultDate (Date) the date to use if no other supplied, may be null 604 @param currentDate (Date) the current date as a possible basis for relative dates, 605 if null today is used (optional) 606 @param dateFormat (string) the expected date format - see formatDate above (optional) 607 @param settings (object) attributes include: 608 shortYearCutoff (number) the cutoff year for determining the century (optional) 609 dayNamesShort (string[7]) abbreviated names of the days from Sunday (optional) 610 dayNames (string[7]) names of the days from Sunday (optional) 611 monthNamesShort (string[12]) abbreviated names of the months (optional) 612 monthNames (string[12]) names of the months (optional) 613 @return (Date) the decoded date */ 614 determineDate: function(dateSpec, defaultDate, currentDate, dateFormat, settings) { 615 if (currentDate && typeof currentDate != 'object') { 616 settings = dateFormat; 617 dateFormat = currentDate; 618 currentDate = null; 619 } 620 if (typeof dateFormat != 'string') { 621 settings = dateFormat; 622 dateFormat = ''; 623 } 624 var offsetString = function(offset) { 625 try { 626 return $.datepick.parseDate(dateFormat, offset, settings); 627 } 628 catch (e) { 629 // Ignore 630 } 631 offset = offset.toLowerCase(); 632 var date = (offset.match(/^c/) && currentDate ? $.datepick.newDate(currentDate) : null) || 633 $.datepick.today(); 634 var pattern = /([+-]?[0-9]+)\s*(d|w|m|y)?/g; 635 var matches = pattern.exec(offset); 636 while (matches) { 637 date = $.datepick.add(date, parseInt(matches[1], 10), matches[2] || 'd'); 638 matches = pattern.exec(offset); 639 } 640 return date; 641 }; 642 defaultDate = (defaultDate ? $.datepick.newDate(defaultDate) : null); 643 dateSpec = (dateSpec == null ? defaultDate : 644 (typeof dateSpec == 'string' ? offsetString(dateSpec) : (typeof dateSpec == 'number' ? 645 (isNaN(dateSpec) || dateSpec == Infinity || dateSpec == -Infinity ? defaultDate : 646 $.datepick.add($.datepick.today(), dateSpec, 'd')) : $.datepick._normaliseDate(dateSpec)))); 647 return dateSpec; 648 }, 649 650 /* Find the number of days in a given month. 651 @param year (Date) the date to get days for or 652 (number) the full year 653 @param month (number) the month (1 to 12) 654 @return (number) the number of days in this month */ 655 daysInMonth: function(year, month) { 656 month = (year.getFullYear ? year.getMonth() + 1 : month); 657 year = (year.getFullYear ? year.getFullYear() : year); 658 return this.newDate(year, month + 1, 0).getDate(); 659 }, 660 661 /* Calculate the day of the year for a date. 662 @param year (Date) the date to get the day-of-year for or 663 (number) the full year 664 @param month (number) the month (1-12) 665 @param day (number) the day 666 @return (number) the day of the year */ 667 dayOfYear: function(year, month, day) { 668 var date = (year.getFullYear ? year : this.newDate(year, month, day)); 669 var newYear = this.newDate(date.getFullYear(), 1, 1); 670 return Math.floor((date.getTime() - newYear.getTime()) / this._msPerDay) + 1; 671 }, 672 673 /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. 674 @param year (Date) the date to get the week for or 675 (number) the full year 676 @param month (number) the month (1-12) 677 @param day (number) the day 678 @return (number) the number of the week within the year that contains this date */ 679 iso8601Week: function(year, month, day) { 680 var checkDate = (year.getFullYear ? 681 new Date(year.getTime()) : this.newDate(year, month, day)); 682 // Find Thursday of this week starting on Monday 683 checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); 684 var time = checkDate.getTime(); 685 checkDate.setMonth(0, 1); // Compare with Jan 1 686 return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; 687 }, 688 689 /* Return today's date. 690 @return (Date) today */ 691 today: function() { 692 return this._normaliseDate(new Date()); 693 }, 694 695 /* Return a new date. 696 @param year (Date) the date to clone or 697 (number) the year 698 @param month (number) the month (1-12) 699 @param day (number) the day 700 @return (Date) the date */ 701 newDate: function(year, month, day) { 702 return (!year ? null : (year.getFullYear ? this._normaliseDate(new Date(year.getTime())) : 703 new Date(year, month - 1, day, 12))); 704 }, 705 706 /* Standardise a date into a common format - time portion is 12 noon. 707 @param date (Date) the date to standardise 708 @return (Date) the normalised date */ 709 _normaliseDate: function(date) { 710 if (date) { 711 date.setHours(12, 0, 0, 0); 712 } 713 return date; 714 }, 715 716 /* Set the year for a date. 717 @param date (Date) the original date 718 @param year (number) the new year 719 @return the updated date */ 720 year: function(date, year) { 721 date.setFullYear(year); 722 return this._normaliseDate(date); 723 }, 724 725 /* Set the month for a date. 726 @param date (Date) the original date 727 @param month (number) the new month (1-12) 728 @return the updated date */ 729 month: function(date, month) { 730 date.setMonth(month - 1); 731 return this._normaliseDate(date); 732 }, 733 734 /* Set the day for a date. 735 @param date (Date) the original date 736 @param day (number) the new day of the month 737 @return the updated date */ 738 day: function(date, day) { 739 date.setDate(day); 740 return this._normaliseDate(date); 741 }, 742 743 /* Add a number of periods to a date. 744 @param date (Date) the original date 745 @param amount (number) the number of periods 746 @param period (string) the type of period d/w/m/y 747 @return the updated date */
748 add: function(date, amount, period) { 749 if (period == 'd' || period == 'w') { 750 this._normaliseDate(date); 751 date.setDate(date.getDate() + amount * (period == 'w' ? 7 : 1)); 752 } 753 else { 754 var year = date.getFullYear() + (period == 'y' ? amount : 0); 755 var month = date.getMonth() + (period == 'm' ? amount : 0); 756 date.setTime($.datepick.newDate(year, month + 1, 757 Math.min(date.getDate(), this.daysInMonth(year, month + 1))).getTime()); 758 } 759 return date; 760 }, 761 762 /* Attach the datepicker functionality to an input field. 763 @param target (element) the control to affect 764 @param settings (object) the custom options for this instance */ 765 _attachPicker: function(target, settings) { 766 target = $(target); 767 if (target.hasClass(this.markerClass)) { 768 return; 769 } 770 target.addClass(this.markerClass); 771 var inst = {target: target, selectedDates: [], drawDate: null, pickingRange: false, 772 inline: ($.inArray(target[0].nodeName.toLowerCase(), ['div', 'span']) > -1), 773 get: function(name) { // Get a setting value, defaulting if necessary 774 var value = this.settings[name] !== undefined ? 775 this.settings[name] : $.datepick._defaults[name]; 776 if ($.inArray(name, ['defaultDate', 'minDate', 'maxDate']) > -1) { // Decode date settings 777 value = $.datepick.determineDate( 778 value, null, this.selectedDates[0], this.get('dateFormat'), inst.getConfig()); 779 } 780 return value; 781 }, 782 curMinDate: function() { 783 return (this.pickingRange ? this.selectedDates[0] : this.get('minDate')); 784 }, 785 getConfig: function() { 786 return {dayNamesShort: this.get('dayNamesShort'), dayNames: this.get('dayNames'), 787 monthNamesShort: this.get('monthNamesShort'), monthNames: this.get('monthNames'), 788 calculateWeek: this.get('calculateWeek'), 789 shortYearCutoff: this.get('shortYearCutoff')}; 790 } 791 }; 792 $.data(target[0], this.dataName, inst); 793 var inlineSettings = ($.fn.metadata ? target.metadata() : {}); 794 inst.settings = $.extend({}, settings || {}, inlineSettings || {}); 795 if (inst.inline) { 796 this._update(target[0]); 797 if ($.fn.mousewheel) { 798 target.mousewheel(this._doMouseWheel); 799 } 800 } 801 else { 802 this._attachments(target, inst); 803 target.bind('keydown.' + this.dataName, this._keyDown). 804 bind('keypress.' + this.dataName, this._keyPress). 805 bind('keyup.' + this.dataName, this._keyUp); 806 if (target.attr('disabled')) { 807 this.disable(target[0]); 808 } 809 } 810 }, 811 812 /* Retrieve the settings for a datepicker control. 813 @param target (element) the control to affect 814 @param name (string) the name of the setting (optional) 815 @return (object) the current instance settings (name == 'all') or 816 (object) the default settings (no name) or 817 (any) the setting value (name supplied) */ 818 options: function(target, name) { 819 var inst = $.data(target, this.dataName); 820 return (inst ? (name ? (name == 'all' ? 821 inst.settings : inst.settings[name]) : $.datepick._defaults) : {}); 822 }, 823 824 /* Reconfigure the settings for a datepicker control. 825 @param target (element) the control to affect 826 @param settings (object) the new options for this instance or 827 (string) an individual property name 828 @param value (any) the individual property value (omit if settings is an object) */ 829 option: function(target, settings, value) { 830 target = $(target); 831 if (!target.hasClass(this.markerClass)) { 832 return; 833 } 834 settings = settings || {}; 835 if (typeof settings == 'string') { 836 var name = settings; 837 settings = {}; 838 settings[name] = value; 839 } 840 var inst = $.data(target[0], this.dataName); 841 var dates = inst.selectedDates; 842 extendRemove(inst.settings, settings); 843 this.setDate(target[0], dates, null, false, true); 844 inst.pickingRange = false; 845 inst.drawDate = $.datepick.newDate(this._checkMinMax( 846 (settings.defaultDate ? inst.get('defaultDate') : inst.drawDate) || 847 inst.get('defaultDate') || $.datepick.today(), inst)); 848 if (!inst.inline) { 849 this._attachments(target, inst); 850 } 851 if (inst.inline || inst.div) { 852 this._update(target[0]); 853 } 854 }, 855 856 /* Attach events and trigger, if necessary. 857 @param target (jQuery) the control to affect
858 @param inst (object) the current instance settings */ 859 _attachments: function(target, inst) { 860 target.unbind('focus.' + this.dataName); 861 if (inst.get('showOnFocus')) { 862 target.bind('focus.' + this.dataName, this.show); 863 } 864 if (inst.trigger) { 865 inst.trigger.remove(); 866 } 867 var trigger = inst.get('showTrigger'); 868 inst.trigger = (!trigger ? $([]) : 869 $(trigger).clone().removeAttr('id').addClass(this._triggerClass) 870 [inst.get('isRTL') ? 'insertBefore' : 'insertAfter'](target). 871 click(function() { 872 if (!$.datepick.isDisabled(target[0])) { 873 $.datepick[$.datepick.curInst == inst ? 874 'hide' : 'show'](target[0]); 875 } 876 })); 877 this._autoSize(target, inst); 878 var dates = this._extractDates(inst, target.val()); 879 if (dates) { 880 this.setDate(target[0], dates, null, true); 881 } 882 if (inst.get('selectDefaultDate') && inst.get('defaultDate') && 883 inst.selectedDates.length == 0) { 884 this.setDate(target[0], $.datepick.newDate(inst.get('defaultDate') || $.datepick.today())); 885 } 886 }, 887 888 /* Apply the maximum length for the date format. 889 @param inst (object) the current instance settings */ 890 _autoSize: function(target, inst) { 891 if (inst.get('autoSize') && !inst.inline) { 892 var date = $.datepick.newDate(2009, 10, 20); // Ensure double digits 893 var dateFormat = inst.get('dateFormat'); 894 if (dateFormat.match(/[DM]/)) { 895 var findMax = function(names) { 896 var max = 0; 897 var maxI = 0; 898 for (var i = 0; i < names.length; i++) { 899 if (names[i].length > max) { 900 max = names[i].length; 901 maxI = i; 902 } 903 } 904 return maxI; 905 }; 906 date.setMonth(findMax(inst.get(dateFormat.match(/MM/) ? // Longest month 907 'monthNames' : 'monthNamesShort'))); 908 date.setDate(findMax(inst.get(dateFormat.match(/DD/) ? // Longest day 909 'dayNames' : 'dayNamesShort')) + 20 - date.getDay()); 910 } 911 inst.target.attr('size', $.datepick.formatDate(dateFormat, date, inst.getConfig()).length); 912 } 913 }, 914 915 /* Remove the datepicker functionality from a control. 916 @param target (element) the control to affect */ 917 destroy: function(target) { 918 target = $(target); 919 if (!target.hasClass(this.markerClass)) { 920 return; 921 } 922 var inst = $.data(target[0], this.dataName); 923 if (inst.trigger) { 924 inst.trigger.remove(); 925 } 926 target.removeClass(this.markerClass).empty().unbind('.' + this.dataName); 927 if (inst.inline && $.fn.mousewheel) { 928 target.unmousewheel(); 929 } 930 if (!inst.inline && inst.get('autoSize')) { 931 target.removeAttr('size'); 932 } 933 $.removeData(target[0], this.dataName); 934 }, 935 936 /* Apply multiple event functions. 937 Usage, for example: onShow: multipleEvents(fn1, fn2, ...) 938 @param fns (function...) the functions to apply */ 939 multipleEvents: function(fns) { 940 var funcs = arguments; 941 return function(args) { 942 for (var i = 0; i < funcs.length; i++) { 943 funcs[i].apply(this, arguments); 944 } 945 }; 946 }, 947 948 /* Enable the datepicker and any associated trigger. 949 @param target (element) the control to use */ 950 enable: function(target) { 951 var $target = $(target); 952 if (!$target.hasClass(this.markerClass)) { 953 return; 954 } 955 var inst = $.data(target, this.dataName); 956 if (inst.inline) 957 $target.children('.' + this._disableClass).remove().end(). 958 find('button,select').attr('disabled', '').end(). 959 find('a').attr('href', 'javascript:void(0)'); 960 else { 961 target.disabled = false; 962 inst.trigger.filter('button.' + this._triggerClass). 963 attr('disabled', '').end(). 964 filter('img.' + this._triggerClass). 965 css({opacity: '1.0', cursor: ''}); 966 } 967 this._disabled = $.map(this._disabled, 968 function(value) { return (value == target ? null : value); }); // Delete entry 969 }, 970 971 /* Disable the datepicker and any associated trigger. 972 @param target (element) the control to use */ 973 disable: function(target) { 974 var $target = $(target); 975 if (!$target.hasClass(this.markerClass)) 976 return; 977 var inst = $.data(target, this.dataName); 978 if (inst.inline) { 979 var inline = $target.children(':last'); 980 var offset = inline.offset();
981 var relOffset = {left: 0, top: 0}; 982 inline.parents().each(function() { 983 if ($(this).css('position') == 'relative') { 984 relOffset = $(this).offset(); 985 return false; 986 } 987 }); 988 var zIndex = $target.css('zIndex'); 989 zIndex = (zIndex == 'auto' ? 0 : parseInt(zIndex, 10)) + 1; 990 $target.prepend('<div class="' + this._disableClass + '" style="' + 991 'width: ' + inline.outerWidth() + 'px; height: ' + inline.outerHeight() + 992 'px; left: ' + (offset.left - relOffset.left) + 'px; top: ' + 993 (offset.top - relOffset.top) + 'px; z-index: ' + zIndex + '"></div>'). 994 find('button,select').attr('disabled', 'disabled').end(). 995 find('a').removeAttr('href'); 996 } 997 else { 998 target.disabled = true; 999 inst.trigger.filter('button.' + this._triggerClass). 1000 attr('disabled', 'disabled').end(). 1001 filter('img.' + this._triggerClass). 1002 css({opacity: '0.5', cursor: 'default'}); 1003 } 1004 this._disabled = $.map(this._disabled, 1005 function(value) { return (value == target ? null : value); }); // Delete entry 1006 this._disabled.push(target); 1007 }, 1008 1009 /* Is the first field in a jQuery collection disabled as a datepicker? 1010 @param target (element) the control to examine 1011 @return (boolean) true if disabled, false if enabled */ 1012 isDisabled: function(target) { 1013 return (target && $.inArray(target, this._disabled) > -1); 1014 }, 1015 1016 /* Show a popup datepicker. 1017 @param target (event) a focus event or 1018 (element) the control to use */ 1019 show: function(target) { 1020 target = target.target || target; 1021 var inst = $.data(target, $.datepick.dataName); 1022 if ($.datepick.curInst == inst) { 1023 return; 1024 } 1025 if ($.datepick.curInst) { 1026 $.datepick.hide($.datepick.curInst, true); 1027 } 1028 if (inst) { 1029 // Retrieve existing date(s) 1030 inst.lastVal = null; 1031 inst.selectedDates = $.datepick._extractDates(inst, $(target).val()); 1032 inst.pickingRange = false; 1033 inst.drawDate = $.datepick._checkMinMax($.datepick.newDate(inst.selectedDates[0] || 1034 inst.get('defaultDate') || $.datepick.today()), inst); 1035 inst.prevDate = $.datepick.newDate(inst.drawDate); 1036 $.datepick.curInst = inst; 1037 // Generate content 1038 $.datepick._update(target, true); 1039 // Adjust position before showing 1040 var offset = $.datepick._checkOffset(inst); 1041 inst.div.css({left: offset.left, top: offset.top}); 1042 // And display 1043 var showAnim = inst.get('showAnim'); 1044 var showSpeed = inst.get('showSpeed'); 1045 showSpeed = (showSpeed == 'normal' && $.ui && $.ui.version >= '1.8' ? 1046 '_default' : showSpeed); 1047 var postProcess = function() { 1048 var borders = $.datepick._getBorders(inst.div); 1049 inst.div.find('.' + $.datepick._coverClass). // IE6- only 1050 css({left: -borders[0], top: -borders[1], 1051 width: inst.div.outerWidth() + borders[0], 1052 height: inst.div.outerHeight() + borders[1]}); 1053 }; 1054 if ($.effects && $.effects[showAnim]) { 1055 var data = inst.div.data(); // Update old effects data 1056 for (var key in data) { 1057 if (key.match(/^ec\.storage\./)) { 1058 data[key] = inst._mainDiv.css(key.replace(/ec\.storage\./, '')); 1059 } 1060 } 1061 inst.div.data(data).show(showAnim, inst.get('showOptions'), showSpeed, postProcess); 1062 } 1063 else { 1064 inst.div[showAnim || 'show']((showAnim ? showSpeed : ''), postProcess); 1065 } 1066 if (!showAnim) { 1067 postProcess(); 1068 } 1069 } 1070 }, 1071 1072 /* Extract possible dates from a string. 1073 @param inst (object) the current instance settings 1074 @param text (string) the text to extract from 1075 @return (CDate[]) the extracted dates */ 1076 _extractDates: function(inst, datesText) { 1077 if (datesText == inst.lastVal) { 1078 return; 1079 } 1080 inst.lastVal = datesText; 1081 var dateFormat = inst.get('dateFormat'); 1082 var multiSelect = inst.get('multiSelect'); 1083 var rangeSelect = inst.get('rangeSelect'); 1084 datesText = datesText.split(multiSelect ? inst.get('multiSeparator') : 1085 (rangeSelect ? inst.get('rangeSeparator') : '\x00')); 1086 var dates = []; 1087 for (var i = 0; i < datesText.length; i++) { 1088 try { 1089 var date = $.datepick.parseDate(dateFormat, datesText[i], inst.getConfig()); 1090 if (date) { 1091 var found = false; 1092 for (var j = 0; j < dates.length; j++) { 1093 if (dates[j].getTime() == date.getTime()) { 1094 found = true; 1095 break; 1096 } 1097 } 1098 if (!found) { 1099 dates.push(date); 1100 } 1101 } 1102 } 1103 catch (e) { 1104 // Ignore 1105 } 1106 } 1107 dates.splice(multiSelect || (rangeSelect ? 2 : 1), dates.length); 1108 if (rangeSelect && dates.length == 1) { 1109 dates[1] = dates[0]; 1110 } 1111 return dates; 1112 }, 1113 1114 /* Update the datepicker display. 1115 @param target (event) a focus event or 1116 (element) the control to use 1117 @param hidden (boolean) true to initially hide the datepicker */ 1118 _update: function(target, hidden) { 1119 target = $(target.target || target); 1120 var inst = $.data(target[0], $.datepick.dataName); 1121 if (inst) { 1122 if (inst.inline) { 1123 target.html(this._generateContent(target[0], inst)); 1124 } 1125 else if ($.datepick.curInst == inst) { 1126 if (!inst.div) {
1127 inst.div = $('<div></div>').addClass(this._popupClass). 1128 css({display: (hidden ? 'none' : 'static'), position: 'absolute', 1129 left: target.offset().left, 1130 top: target.offset().top + target.outerHeight()}). 1131 appendTo($(inst.get('popupContainer') || 'body')); 1132 if ($.fn.mousewheel) { 1133 inst.div.mousewheel(this._doMouseWheel); 1134 } 1135 } 1136 inst.div.html(this._generateContent(target[0], inst)); 1137 target.focus(); 1138 } 1139 if (inst.inline || $.datepick.curInst == inst) { 1140 var onChange = inst.get('onChangeMonthYear'); 1141 if (onChange && (!inst.prevDate || 1142 inst.prevDate.getFullYear() != inst.drawDate.getFullYear() || 1143 inst.prevDate.getMonth() != inst.drawDate.getMonth())) { 1144 onChange.apply(target[0], [inst.drawDate.getFullYear(), inst.drawDate.getMonth() + 1]); 1145 } 1146 } 1147 } 1148 }, 1149 1150 /* Update the input field and any alternate field with the current dates. 1151 @param target (element) the control to use 1152 @param keyUp (boolean, internal) true if coming from keyUp processing */ 1153 _updateInput: function(target, keyUp) { 1154 var inst = $.data(target, this.dataName); 1155 if (inst) { 1156 var value = ''; 1157 var altValue = ''; 1158 var sep = (inst.get('multiSelect') ? inst.get('multiSeparator') : 1159 inst.get('rangeSeparator')); 1160 var dateFormat = inst.get('dateFormat'); 1161 var altFormat = inst.get('altFormat') || dateFormat; 1162 for (var i = 0; i < inst.selectedDates.length; i++) { 1163 value += (keyUp ? '' : (i > 0 ? sep : '') + $.datepick.formatDate( 1164 dateFormat, inst.selectedDates[i], inst.getConfig())); 1165 altValue += (i > 0 ? sep : '') + $.datepick.formatDate( 1166 altFormat, inst.selectedDates[i], inst.getConfig()); 1167 } 1168 if (!inst.inline && !keyUp) { 1169 $(target).val(value); 1170 } 1171 $(inst.get('altField')).val(altValue); 1172 var onSelect = inst.get('onSelect'); 1173 if (onSelect && !keyUp && !inst.inSelect) { 1174 inst.inSelect = true; // Prevent endless loops 1175 onSelect.apply(target, [inst.selectedDates]); 1176 inst.inSelect = false; 1177 } 1178 } 1179 }, 1180 1181 /* Retrieve the size of left and top borders for an element. 1182 @param elem (jQuery) the element of interest 1183 @return (number[2]) the left and top borders */ 1184 _getBorders: function(elem) { 1185 var convert = function(value) { 1186 var extra = ($.browser.msie ? 1 : 0); 1187 return {thin: 1 + extra, medium: 3 + extra, thick: 5 + extra}[value] || value; 1188 }; 1189 return [parseFloat(convert(elem.css('border-left-width'))), 1190 parseFloat(convert(elem.css('border-top-width')))]; 1191 }, 1192 1193 /* Check positioning to remain on the screen. 1194 @param inst (object) the current instance settings 1195 @return (object) the updated offset for the datepicker */ 1196 _checkOffset: function(inst) { 1197 var base = (inst.target.is(':hidden') && inst.trigger ? inst.trigger : inst.target); 1198 var offset = base.offset(); 1199 var isFixed = false; 1200 $(inst.target).parents().each(function() { 1201 isFixed |= $(this).css('position') == 'fixed'; 1202 return !isFixed; 1203 }); 1204 if (isFixed && $.browser.opera) { // Correction for Opera when fixed and scrolled 1205 offset.left -= document.documentElement.scrollLeft; 1206 offset.top -= document.documentElement.scrollTop; 1207 } 1208 var browserWidth = (!$.browser.mozilla || document.doctype ? 1209 document.documentElement.clientWidth : 0) || document.body.clientWidth; 1210 var browserHeight = (!$.browser.mozilla || document.doctype ? 1211 document.documentElement.clientHeight : 0) || document.body.clientHeight; 1212 if (browserWidth == 0) { 1213 return offset; 1214 } 1215 var alignment = inst.get('alignment'); 1216 var isRTL = inst.get('isRTL'); 1217 var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; 1218 var scrollY = document.documentElement.scrollTop || document.body.scrollTop; 1219 var above = offset.top - inst.div.outerHeight() - 1220 (isFixed && $.browser.opera ? document.documentElement.scrollTop : 0); 1221 var below = offset.top + base.outerHeight(); 1222 var alignL = offset.left; 1223 var alignR = offset.left + base.outerWidth() - inst.div.outerWidth() - 1224 (isFixed && $.browser.opera ? document.documentElement.scrollLeft : 0); 1225 var tooWide = (offset.left + inst.div.outerWidth() - scrollX) > browserWidth; 1226 var tooHigh = (offset.top + inst.target.outerHeight() + inst.div.outerHeight() - 1227 scrollY) > browserHeight; 1228 if (alignment == 'topLeft') {
1229 offset = {left: alignL, top: above}; 1230 } 1231 else if (alignment == 'topRight') { 1232 offset = {left: alignR, top: above}; 1233 } 1234 else if (alignment == 'bottomLeft') { 1235 offset = {left: alignL, top: below}; 1236 } 1237 else if (alignment == 'bottomRight') { 1238 offset = {left: alignR, top: below}; 1239 } 1240 else if (alignment == 'top') { 1241 offset = {left: (isRTL || tooWide ? alignR : alignL), top: above}; 1242 } 1243 else { // bottom 1244 offset = {left: (isRTL || tooWide ? alignR : alignL), 1245 top: (tooHigh ? above : below)}; 1246 } 1247 offset.left = Math.max((isFixed ? 0 : scrollX), offset.left - (isFixed ? scrollX : 0)); 1248 offset.top = Math.max((isFixed ? 0 : scrollY), offset.top - (isFixed ? scrollY : 0)); 1249 return offset; 1250 }, 1251 1252 /* Close date picker if clicked elsewhere. 1253 @param event (MouseEvent) the mouse click to check */ 1254 _checkExternalClick: function(event) { 1255 if (!$.datepick.curInst) { 1256 return; 1257 } 1258 var target = $(event.target); 1259 if (!target.parents().andSelf().hasClass($.datepick._popupClass) && 1260 !target.hasClass($.datepick.markerClass) && 1261 !target.parents().andSelf().hasClass($.datepick._triggerClass)) { 1262 $.datepick.hide($.datepick.curInst); 1263 } 1264 }, 1265 1266 /* Hide a popup datepicker. 1267 @param target (element) the control to use or 1268 (object) the current instance settings 1269 @param immediate (boolean) true to close immediately without animation */ 1270 hide: function(target, immediate) { 1271 var inst = $.data(target, this.dataName) || target; 1272 if (inst && inst == $.datepick.curInst) { 1273 var showAnim = (immediate ? '' : inst.get('showAnim')); 1274 var showSpeed = inst.get('showSpeed'); 1275 showSpeed = (showSpeed == 'normal' && $.ui && $.ui.version >= '1.8' ? 1276 '_default' : showSpeed); 1277 var postProcess = function() { 1278 inst.div.remove(); 1279 inst.div = null; 1280 $.datepick.curInst = null; 1281 var onClose = inst.get('onClose'); 1282 if (onClose) { 1283 onClose.apply(target, [inst.selectedDates]); 1284 } 1285 }; 1286 inst.div.stop(); 1287 if ($.effects && $.effects[showAnim]) { 1288 inst.div.hide(showAnim, inst.get('showOptions'), showSpeed, postProcess); 1289 } 1290 else { 1291 var hideAnim = (showAnim == 'slideDown' ? 'slideUp' : 1292 (showAnim == 'fadeIn' ? 'fadeOut' : 'hide')); 1293 inst.div[hideAnim]((showAnim ? showSpeed : ''), postProcess); 1294 } 1295 if (!showAnim) { 1296 postProcess(); 1297 } 1298 } 1299 }, 1300 1301 /* Handle keystrokes in the datepicker. 1302 @param event (KeyEvent) the keystroke 1303 @return (boolean) true if not handled, false if handled */ 1304 _keyDown: function(event) { 1305 var target = event.target; 1306 var inst = $.data(target, $.datepick.dataName); 1307 var handled = false; 1308 if (inst.div) { 1309 if (event.keyCode == 9) { // Tab - close 1310 $.datepick.hide(target); 1311 } 1312 else if (event.keyCode == 13) { // Enter - select 1313 $.datepick.selectDate(target, 1314 $('a.' + inst.get('renderer').highlightedClass, inst.div)[0]); 1315 handled = true; 1316 } 1317 else { // Command keystrokes 1318 var commands = inst.get('commands'); 1319 for (var name in commands) { 1320 var command = commands[name]; 1321 if (command.keystroke.keyCode == event.keyCode && 1322 !!command.keystroke.ctrlKey == !!(event.ctrlKey || event.metaKey) && 1323 !!command.keystroke.altKey == event.altKey && 1324 !!command.keystroke.shiftKey == event.shiftKey) { 1325 $.datepick.performAction(target, name); 1326 handled = true; 1327 break; 1328 } 1329 } 1330 } 1331 } 1332 else { // Show on 'current' keystroke 1333 var command = inst.get('commands').current; 1334 if (command.keystroke.keyCode == event.keyCode && 1335 !!command.keystroke.ctrlKey == !!(event.ctrlKey || event.metaKey) && 1336 !!command.keystroke.altKey == event.altKey && 1337 !!command.keystroke.shiftKey == event.shiftKey) { 1338 $.datepick.show(target); 1339 handled = true; 1340 } 1341 } 1342 inst.ctrlKey = ((event.keyCode < 48 && event.keyCode != 32) || 1343 event.ctrlKey || event.metaKey); 1344 if (handled) { 1345 event.preventDefault(); 1346 event.stopPropagation(); 1347 } 1348 return !handled; 1349 }, 1350 1351 /* Filter keystrokes in the datepicker. 1352 @param event (KeyEvent) the keystroke 1353 @return (boolean) true if allowed, false if not allowed */ 1354 _keyPress: function(event) { 1355 var target = event.target; 1356 var inst = $.data(target, $.datepick.dataName); 1357 if (inst && inst.get('constrainInput')) { 1358 var ch = String.fromCharCode(event.keyCode || event.charCode); 1359 var allowedChars = $.datepick._allowedChars(inst); 1360 return (event.metaKey || inst.ctrlKey || ch < ' ' || 1361 !allowedChars || allowedChars.indexOf(ch) > -1); 1362 } 1363 return true; 1364 }, 1365 1366 /* Determine the set of characters allowed by the date format. 1367 @param inst (object) the current instance settings 1368 @return (string) the set of allowed characters, or null if anything allowed */ 1369 _allowedChars: function(inst) { 1370 var dateFormat = inst.get('dateFormat'); 1371 var allowedChars = (inst.get('multiSelect') ? inst.get('multiSeparator') : 1372 (inst.get('rangeSelect') ? inst.get('rangeSeparator') : '')); 1373 var literal = false; 1374 var hasNum = false; 1375 for (var i = 0; i < dateFormat.length; i++) { 1376 var ch = dateFormat.charAt(i); 1377 if (literal) { 1378 if (ch == "'" && dateFormat.charAt(i + 1) != "'") { 1379 literal = false; 1380 } 1381 else { 1382 allowedChars += ch; 1383 } 1384 } 1385 else { 1386 switch (ch) { 1387 case 'd': case 'm': case 'o': case 'w': 1388 allowedChars += (hasNum ? '' : '0123456789'); hasNum = true; break; 1389 case 'y': case '@': case '!': 1390 allowedChars += (hasNum ? '' : '0123456789') + '-'; hasNum = true; break; 1391 case 'J': 1392 allowedChars += (hasNum ? '' : '0123456789') + '-.'; hasNum = true; break; 1393 case 'D': case 'M': case 'Y': 1394 return null; // Accept anything
1395 case "'": 1396 if (dateFormat.charAt(i + 1) == "'") { 1397 allowedChars += "'"; 1398 } 1399 else { 1400 literal = true; 1401 } 1402 break; 1403 default: 1404 allowedChars += ch; 1405 } 1406 } 1407 } 1408 return allowedChars; 1409 }, 1410 1411 /* Synchronise datepicker with the field. 1412 @param event (KeyEvent) the keystroke 1413 @return (boolean) true if allowed, false if not allowed */ 1414 _keyUp: function(event) { 1415 var target = event.target; 1416 var inst = $.data(target, $.datepick.dataName); 1417 if (inst && !inst.ctrlKey && inst.lastVal != inst.target.val()) { 1418 try { 1419 var dates = $.datepick._extractDates(inst, inst.target.val()); 1420 if (dates.length > 0) { 1421 $.datepick.setDate(target, dates, null, true); 1422 } 1423 } 1424 catch (event) { 1425 // Ignore 1426 } 1427 } 1428 return true; 1429 }, 1430 1431 /* Increment/decrement month/year on mouse wheel activity. 1432 @param event (event) the mouse wheel event 1433 @param delta (number) the amount of change */ 1434 _doMouseWheel: function(event, delta) { 1435 var target = ($.datepick.curInst && $.datepick.curInst.target[0]) || 1436 $(event.target).closest('.' + $.datepick.markerClass)[0]; 1437 if ($.datepick.isDisabled(target)) { 1438 return; 1439 } 1440 var inst = $.data(target, $.datepick.dataName); 1441 if (inst.get('useMouseWheel')) { 1442 delta = ($.browser.opera ? -delta : delta); 1443 delta = (delta < 0 ? -1 : +1); 1444 $.datepick.changeMonth(target, 1445 -inst.get(event.ctrlKey ? 'monthsToJump' : 'monthsToStep') * delta); 1446 } 1447 event.preventDefault(); 1448 }, 1449 1450 /* Clear an input and close a popup datepicker. 1451 @param target (element) the control to use */ 1452 clear: function(target) { 1453 var inst = $.data(target, this.dataName); 1454 if (inst) { 1455 inst.selectedDates = []; 1456 this.hide(target); 1457 if (inst.get('selectDefaultDate') && inst.get('defaultDate')) { 1458 this.setDate(target, 1459 $.datepick.newDate(inst.get('defaultDate') ||$.datepick.today())); 1460 } 1461 else { 1462 this._updateInput(target); 1463 } 1464 } 1465 }, 1466 1467 /* Retrieve the selected date(s) for a datepicker. 1468 @param target (element) the control to examine 1469 @return (CDate[]) the selected date(s) */ 1470 getDate: function(target) { 1471 var inst = $.data(target, this.dataName); 1472 return (inst ? inst.selectedDates : []); 1473 }, 1474 1475 /* Set the selected date(s) for a datepicker. 1476 @param target (element) the control to examine 1477 @param dates (CDate or number or string or [] of these) the selected date(s) 1478 @param endDate (CDate or number or string) the ending date for a range (optional) 1479 @param keyUp (boolean, internal) true if coming from keyUp processing 1480 @param setOpt (boolean, internal) true if coming from option processing */ 1481 setDate: function(target, dates, endDate, keyUp, setOpt) { 1482 var inst = $.data(target, this.dataName); 1483 if (inst) { 1484 if (!$.isArray(dates)) { 1485 dates = [dates]; 1486 if (endDate) { 1487 dates.push(endDate); 1488 } 1489 } 1490 var dateFormat = inst.get('dateFormat'); 1491 var minDate = inst.get('minDate'); 1492 var maxDate = inst.get('maxDate'); 1493 var curDate = inst.selectedDates[0]; 1494 inst.selectedDates = []; 1495 for (var i = 0; i < dates.length; i++) { 1496 var date = $.datepick.determineDate( 1497 dates[i], null, curDate, dateFormat, inst.getConfig()); 1498 if (date) { 1499 if ((!minDate || date.getTime() >= minDate.getTime()) && 1500 (!maxDate || date.getTime() <= maxDate.getTime())) { 1501 var found = false; 1502 for (var j = 0; j < inst.selectedDates.length; j++) { 1503 if (inst.selectedDates[j].getTime() == date.getTime()) { 1504 found = true; 1505 break; 1506 } 1507 } 1508 if (!found) { 1509 inst.selectedDates.push(date); 1510 } 1511 } 1512 } 1513 } 1514 var rangeSelect = inst.get('rangeSelect'); 1515 inst.selectedDates.splice(inst.get('multiSelect') || 1516 (rangeSelect ? 2 : 1), inst.selectedDates.length); 1517 if (rangeSelect) { 1518 switch (inst.selectedDates.length) { 1519 case 1: inst.selectedDates[1] = inst.selectedDates[0]; break; 1520 case 2: inst.selectedDates[1] = 1521 (inst.selectedDates[0].getTime() > inst.selectedDates[1].getTime() ? 1522 inst.selectedDates[0] : inst.selectedDates[1]); break; 1523 } 1524 inst.pickingRange = false; 1525 } 1526 inst.prevDate = (inst.drawDate ? $.datepick.newDate(inst.drawDate) : null); 1527 inst.drawDate = this._checkMinMax($.datepick.newDate(inst.selectedDates[0] || 1528 inst.get('defaultDate') || $.datepick.today()), inst); 1529 if (!setOpt) { 1530 this._update(target); 1531 this._updateInput(target, keyUp); 1532 } 1533 } 1534 }, 1535 1536 /* Determine whether a date is selectable for this datepicker. 1537 @param target (element) the control to check 1538 @param date (Date or string or number) the date to check 1539 @return (boolean) true if selectable, false if not */ 1540 isSelectable: function(target, date) { 1541 var inst = $.data(target, this.dataName); 1542 if (!inst) { 1543 return false; 1544 } 1545 date = $.datepick.determineDate(date, inst.selectedDates[0] || this.today(), null, 1546 inst.get('dateFormat'), inst.getConfig()); 1547 return this._isSelectable(target, date, inst.get('onDate'), 1548 inst.get('minDate'), inst.get('maxDate')); 1549 }, 1550 1551 /* Internally determine whether a date is selectable for this datepicker. 1552 @param target (element) the control to check 1553 @param date (Date) the date to check 1554 @param onDate (function or boolean) any onDate callback or callback.selectable 1555 @param mindate (Date) the minimum allowed date 1556 @param maxdate (Date) the maximum allowed date 1557 @return (boolean) true if selectable, false if not */ 1558 _isSelectable: function(target, date, onDate, minDate, maxDate) { 1559 var dateInfo = (typeof onDate == 'boolean' ? {selectable: onDate} : 1560 (!onDate ? {}
1560 : onDate.apply(target, [date, true]))); 1561 return (dateInfo.selectable != false) && 1562 (!minDate || date.getTime() >= minDate.getTime()) && 1563 (!maxDate || date.getTime() <= maxDate.getTime()); 1564 }, 1565 1566 /* Perform a named action for a datepicker. 1567 @param target (element) the control to affect 1568 @param action (string) the name of the action */ 1569 performAction: function(target, action) { 1570 var inst = $.data(target, this.dataName); 1571 if (inst && !this.isDisabled(target)) { 1572 var commands = inst.get('commands'); 1573 if (commands[action] && commands[action].enabled.apply(target, [inst])) { 1574 commands[action].action.apply(target, [inst]); 1575 } 1576 } 1577 }, 1578 1579 /* Set the currently shown month, defaulting to today's. 1580 @param target (element) the control to affect 1581 @param year (number) the year to show (optional) 1582 @param month (number) the month to show (1-12) (optional) 1583 @param day (number) the day to show (optional) */ 1584 showMonth: function(target, year, month, day) { 1585 var inst = $.data(target, this.dataName); 1586 if (inst && (day != null || 1587 (inst.drawDate.getFullYear() != year || inst.drawDate.getMonth() + 1 != month))) { 1588 inst.prevDate = $.datepick.newDate(inst.drawDate); 1589 var show = this._checkMinMax((year != null ? 1590 $.datepick.newDate(year, month, 1) : $.datepick.today()), inst); 1591 inst.drawDate = $.datepick.newDate(show.getFullYear(), show.getMonth() + 1, 1592 (day != null ? day : Math.min(inst.drawDate.getDate(), 1593 $.datepick.daysInMonth(show.getFullYear(), show.getMonth() + 1)))); 1594 this._update(target); 1595 } 1596 }, 1597 1598 /* Adjust the currently shown month. 1599 @param target (element) the control to affect 1600 @param offset (number) the number of months to change by */ 1601 changeMonth: function(target, offset) { 1602 var inst = $.data(target, this.dataName); 1603 if (inst) { 1604 var date = $.datepick.add($.datepick.newDate(inst.drawDate), offset, 'm'); 1605 this.showMonth(target, date.getFullYear(), date.getMonth() + 1); 1606 } 1607 }, 1608 1609 /* Adjust the currently shown day. 1610 @param target (element) the control to affect 1611 @param offset (number) the number of days to change by */ 1612 changeDay: function(target, offset) { 1613 var inst = $.data(target, this.dataName); 1614 if (inst) { 1615 var date = $.datepick.add($.datepick.newDate(inst.drawDate), offset, 'd'); 1616 this.showMonth(target, date.getFullYear(), date.getMonth() + 1, date.getDate()); 1617 } 1618 }, 1619 1620 /* Restrict a date to the minimum/maximum specified. 1621 @param date (CDate) the date to check 1622 @param inst (object) the current instance settings */ 1623 _checkMinMax: function(date, inst) { 1624 var minDate = inst.get('minDate'); 1625 var maxDate = inst.get('maxDate'); 1626 date = (minDate && date.getTime() < minDate.getTime() ? $.datepick.newDate(minDate) : date); 1627 date = (maxDate && date.getTime() > maxDate.getTime() ? $.datepick.newDate(maxDate) : date); 1628 return date; 1629 }, 1630 1631 /* Retrieve the date associated with an entry in the datepicker. 1632 @param target (element) the control to examine 1633 @param elem (element) the selected datepicker element 1634 @return (CDate) the corresponding date, or null */ 1635 retrieveDate: function(target, elem) { 1636 var inst = $.data(target, this.dataName); 1637 return (!inst ? null : this._normaliseDate( 1638 new Date(parseInt(elem.className.replace(/^.*dp(-?\d+).*$/, '$1'), 10)))); 1639 }, 1640 1641 /* Select a date for this datepicker. 1642 @param target (element) the control to examine 1643 @param elem (element) the selected datepicker element */ 1644 selectDate: function(target, elem) { 1645 var inst = $.data(target, this.dataName); 1646 if (inst && !this.isDisabled(target)) { 1647 var date = this.retrieveDate(target, elem); 1648 var multiSelect = inst.get('multiSelect'); 1649 var rangeSelect = inst.get('rangeSelect'); 1650 if (multiSelect) { 1651 var found = false; 1652 for (var i = 0; i < inst.selectedDates.length; i++) { 1653 if (date.getTime() == inst.selectedDates[i].getTime()) { 1654 inst.selectedDates.splice(i, 1); 1655 found = true; 1656 break; 1657 } 1658 } 1659 if (!found && inst.selectedDates.length < multiSelect) { 1660 inst.selectedDates.push(date); 1661 } 1662 } 1663 else if (rangeSelect) { 1664 if (inst.pickingRange) { 1665 inst.selectedDates[1] = date; 1666 } 1667 else { 1668 inst.selectedDates = [date, date]; 1669 } 1670 inst.pickingRange = !inst.pickingRange; 1671 } 1672 else { 1673 inst.selectedDates = [date]; 1674 } 1675 inst.prevDate = $.datepick.newDate(date); 1676 this._updateInput(target); 1677 if (inst.inline || inst.pickingRange || inst.selectedDates.length < 1678 (multiSelect || (rangeSelect ? 2 : 1))) { 1679 this._update(target); 1680 } 1681 else { 1682 this.hide(target); 1683 } 1684 } 1685 }, 1686 1687 /* Generate the datepicker content for this control. 1688 @param target (element) the control to affect
1689 @param inst (object) the current instance settings 1690 @return (jQuery) the datepicker content */ 1691 _generateContent: function(target, inst) { 1692 var renderer = inst.get('renderer'); 1693 var monthsToShow = inst.get('monthsToShow'); 1694 monthsToShow = ($.isArray(monthsToShow) ? monthsToShow : [1, monthsToShow]); 1695 inst.drawDate = this._checkMinMax( 1696 inst.drawDate || inst.get('defaultDate') || $.datepick.today(), inst); 1697 var drawDate = $.datepick.add( 1698 $.datepick.newDate(inst.drawDate), -inst.get('monthsOffset'), 'm'); 1699 // Generate months 1700 var monthRows = ''; 1701 for (var row = 0; row < monthsToShow[0]; row++) { 1702 var months = ''; 1703 for (var col = 0; col < monthsToShow[1]; col++) { 1704 months += this._generateMonth(target, inst, drawDate.getFullYear(), 1705 drawDate.getMonth() + 1, renderer, (row == 0 && col == 0)); 1706 $.datepick.add(drawDate, 1, 'm'); 1707 } 1708 monthRows += this._prepare(renderer.monthRow, inst).replace(/\{months\}/, months); 1709 } 1710 var picker = this._prepare(renderer.picker, inst).replace(/\{months\}/, monthRows). 1711 replace(/\{weekHeader\}/g, this._generateDayHeaders(inst, renderer)) + 1712 ($.browser.msie && parseInt($.browser.version, 10) < 7 && !inst.inline ? 1713 '<iframe src="javascript:void(0);" class="' + this._coverClass + '"></iframe>' : ''); 1714 // Add commands 1715 var commands = inst.get('commands'); 1716 var asDateFormat = inst.get('commandsAsDateFormat'); 1717 var addCommand = function(type, open, close, name, classes) { 1718 if (picker.indexOf('{' + type + ':' + name + '}') == -1) { 1719 return; 1720 } 1721 var command = commands[name]; 1722 var date = (asDateFormat ? command.date.apply(target, [inst]) : null); 1723 picker = picker.replace(new RegExp('\\{' + type + ':' + name + '\\}', 'g'), 1724 '<' + open + 1725 (command.status ? ' title="' + inst.get(command.status) + '"' : '') + 1726 ' class="' + renderer.commandClass + ' ' + 1727 renderer.commandClass + '-' + name + ' ' + classes + 1728 (command.enabled(inst) ? '' : ' ' + renderer.disabledClass) + '">' + 1729 (date ? $.datepick.formatDate(inst.get(command.text), date, inst.getConfig()) : 1730 inst.get(command.text)) + '</' + close + '>'); 1731 }; 1732 for (var name in commands) { 1733 addCommand('button', 'button type="button"', 'button', name, 1734 renderer.commandButtonClass); 1735 addCommand('link', 'a href="javascript:void(0)"', 'a', name, 1736 renderer.commandLinkClass); 1737 } 1738 picker = $(picker); 1739 if (monthsToShow[1] > 1) { 1740 var count = 0; 1741 $(renderer.monthSelector, picker).each(function() { 1742 var nth = ++count % monthsToShow[1];
1743 $(this).addClass(nth == 1 ? 'first' : (nth == 0 ? 'last' : '')); 1744 }); 1745 } 1746 // Add datepicker behaviour 1747 var self = this; 1748 picker.find(renderer.daySelector + ' a').hover( 1749 function() { $(this).addClass(renderer.highlightedClass); }, 1750 function() { 1751 (inst.inline ? $(this).parents('.' + self.markerClass) : inst.div). 1752 find(renderer.daySelector + ' a'). 1753 removeClass(renderer.highlightedClass); 1754 }). 1755 click(function() { 1756 self.selectDate(target, this); 1757 }).end(). 1758 find('select.' + this._monthYearClass + ':not(.' + this._anyYearClass + ')'). 1759 change(function() { 1760 var monthYear = $(this).val().split('/'); 1761 self.showMonth(target, parseInt(monthYear[1], 10), parseInt(monthYear[0], 10)); 1762 }).end(). 1763 find('select.' + this._anyYearClass). 1764 click(function() { 1765 $(this).css('visibility', 'hidden'). 1766 next('input').css({left: this.offsetLeft, top: this.offsetTop, 1767 width: this.offsetWidth, height: this.offsetHeight}).show().focus(); 1768 }).end(). 1769 find('input.' + self._monthYearClass). 1770 change(function() { 1771 try { 1772 var year = parseInt($(this).val(), 10); 1773 year = (isNaN(year) ? inst.drawDate.getFullYear() : year); 1774 self.showMonth(target, year, inst.drawDate.getMonth() + 1, inst.drawDate.getDate()); 1775 } 1776 catch (e) { 1777 alert(e); 1778 } 1779 }).keydown(function(event) { 1780 if (event.keyCode == 13) { // Enter 1781 $(event.target).change(); 1782 } 1783 else if (event.keyCode == 27) { // Escape 1784 $(event.target).hide().prev('select').css('visibility', 'visible'); 1785 inst.target.focus(); 1786 } 1787 }); 1788 // Add command behaviour 1789 picker.find('.' + renderer.commandClass).click(function() { 1790 if (!$(this).hasClass(renderer.disabledClass)) { 1791 var action = this.className.replace( 1792 new RegExp('^.*' + renderer.commandClass + '-([^ ]+).*$'), '$1'); 1793 $.datepick.performAction(target, action); 1794 } 1795 }); 1796 // Add classes 1797 if (inst.get('isRTL')) { 1798 picker.addClass(renderer.rtlClass); 1799 } 1800 if (monthsToShow[0] * monthsToShow[1] > 1) { 1801 picker.addClass(renderer.multiClass); 1802 } 1803 var pickerClass = inst.get('pickerClass'); 1804 if (pickerClass) { 1805 picker.addClass(pickerClass); 1806 } 1807 // Resize 1808 $('body').append(picker); 1809 var width = 0; 1810 picker.find(renderer.monthSelector).each(function() { 1811 width += $(this).outerWidth(); 1812 }); 1813 picker.width(width / monthsToShow[0]); 1814 // Pre-show customisation 1815 var onShow = inst.get('onShow'); 1816 if (onShow) { 1817 onShow.apply(target, [picker, inst]); 1818 } 1819 return picker; 1820 }, 1821 1822 /* Generate the content for a single month. 1823 @param target (element) the control to affect 1824 @param inst (object) the current instance settings 1825 @param year (number) the year to generate 1826 @param month (number) the month to generate 1827 @param renderer (object) the rendering templates 1828 @param first (boolean) true if first of multiple months 1829 @return (string) the month content */ 1830 _generateMonth: function(target, inst, year, month, renderer, first) { 1831 var daysInMonth = $.datepick.daysInMonth(year, month); 1832 var monthsToShow = inst.get('monthsToShow'); 1833 monthsToShow = ($.isArray(monthsToShow) ? monthsToShow : [1, monthsToShow]); 1834 var fixedWeeks = inst.get('fixedWeeks') || (monthsToShow[0] * monthsToShow[1] > 1); 1835 var firstDay = inst.get('firstDay'); 1836 var leadDays = ($.datepick.newDate(year, month, 1).getDay() - firstDay + 7) % 7; 1837 var numWeeks = (fixedWeeks ? 6 : Math.ceil((leadDays + daysInMonth) / 7)); 1838 var showOtherMonths = inst.get('showOtherMonths'); 1839 var selectOtherMonths = inst.get('selectOtherMonths') && showOtherMonths; 1840 var dayStatus = inst.get('dayStatus'); 1841 var minDate = (inst.pickingRange ? inst.selectedDates[0] : inst.get('minDate')); 1842 var maxDate = inst.get('maxDate'); 1843 var rangeSelect = inst.get('rangeSelect'); 1844 var onDate = inst.get('onDate'); 1845 var showWeeks = renderer.week.indexOf('{weekOfYear}') > -1; 1846 var calculateWeek = inst.get('calculateWeek'); 1847 var today = $.datepick.today(); 1848 var drawDate = $.datepick.newDate(year, month, 1); 1849 $.datepick.add(drawDate, -leadDays - (fixedWeeks && (drawDate.getDay() == firstDay) ? 7 : 0), 'd'); 1850 var ts = drawDate.getTime(); 1851 // Generate weeks 1852 var weeks = ''; 1853 for (var week = 0; week < numWeeks; week++) { 1854 var weekOfYear = (!showWeeks ? '' : '<span class="dp' + ts + '">' + 1855 (calculateWeek ? calculateWeek(drawDate) : 0) + '</span>'); 1856 var days = ''; 1857 for (var day = 0; day < 7; day++) { 1858 var selected = false; 1859 if (rangeSelect && inst.selectedDates.length > 0) { 1860 selected = (drawDate.getTime() >= inst.selectedDates[0] && 1861 drawDate.getTime() <= inst.selectedDates[1]); 1862 } 1863 else { 1864 for (var i = 0; i < inst.selectedDates.length; i++) { 1865 if (inst.selectedDates[i].getTime() == drawDate.getTime()) { 1866 selected = true; 1867 break; 1868 } 1869 } 1870 } 1871 var dateInfo = (!onDate ? {} :
1872 onDate.apply(target, [drawDate, drawDate.getMonth() + 1 == month])); 1873 var selectable = (selectOtherMonths || drawDate.getMonth() + 1 == month) && 1874 this._isSelectable(target, drawDate, dateInfo.selectable, minDate, maxDate); 1875 days += this._prepare(renderer.day, inst).replace(/\{day\}/g, 1876 (selectable ? '<a href="javascript:void(0)"' : '<span') + 1877 ' class="dp' + ts + ' ' + (dateInfo.dateClass || '') + 1878 (selected && (selectOtherMonths || drawDate.getMonth() + 1 == month) ? 1879 ' ' + renderer.selectedClass : '') + 1880 (selectable ? ' ' + renderer.defaultClass : '') + 1881 ((drawDate.getDay() || 7) < 6 ? '' : ' ' + renderer.weekendClass) + 1882 (drawDate.getMonth() + 1 == month ? '' : ' ' + renderer.otherMonthClass) + 1883 (drawDate.getTime() == today.getTime() && (drawDate.getMonth() + 1) == month ? 1884 ' ' + renderer.todayClass : '') + 1885 (drawDate.getTime() == inst.drawDate.getTime() && (drawDate.getMonth() + 1) == month ? 1886 ' ' + renderer.highlightedClass : '') + '"' + 1887 (dateInfo.title || (dayStatus && selectable) ? ' title="' + 1888 (dateInfo.title || $.datepick.formatDate( 1889 dayStatus, drawDate, inst.getConfig())) + '"' : '') + '>' + 1890 (showOtherMonths || (drawDate.getMonth() + 1) == month ? 1891 dateInfo.content || drawDate.getDate() : ' ') + 1892 (selectable ? '</a>' : '</span>')); 1893 $.datepick.add(drawDate, 1, 'd'); 1894 ts = drawDate.getTime(); 1895 } 1896 weeks += this._prepare(renderer.week, inst).replace(/\{days\}/g, days). 1897 replace(/\{weekOfYear\}/g, weekOfYear); 1898 } 1899 var monthHeader = this._prepare(renderer.month, inst).match(/\{monthHeader(:[^\}]+)?\}/); 1900 monthHeader = (monthHeader[0].length <= 13 ? 'MM yyyy' : 1901 monthHeader[0].substring(13, monthHeader[0].length - 1)); 1902 monthHeader = (first ? this._generateMonthSelection( 1903 inst, year, month, minDate, maxDate, monthHeader, renderer) : 1904 $.datepick.formatDate(monthHeader, $.datepick.newDate(year, month, 1), inst.getConfig())); 1905 var weekHeader = this._prepare(renderer.weekHeader, inst). 1906 replace(/\{days\}/g, this._generateDayHeaders(inst, renderer)); 1907 return this._prepare(renderer.month, inst).replace(/\{monthHeader(:[^\}]+)?\}/g, monthHeader). 1908 replace(/\{weekHeader\}/g, weekHeader).replace(/\{weeks\}/g, weeks); 1909 }, 1910 1911 /* Generate the HTML for the day headers. 1912 @param inst (object) the current instance settings 1913 @param renderer (object) the rendering templates 1914 @return (string) a week's worth of day headers */ 1915 _generateDayHeaders: function(inst, renderer) { 1916 var firstDay = inst.get('firstDay'); 1917 var dayNames = inst.get('dayNames'); 1918 var dayNamesMin = inst.get('dayNamesMin'); 1919 var header = ''; 1920 for (var day = 0; day < 7; day++) { 1921 var dow = (day + firstDay) % 7; 1922 header += this._prepare(renderer.dayHeader, inst).replace(/\{day\}/g, 1923 '<span class="' + this._curDoWClass + dow + '" title="' + 1924 dayNames[dow] + '">' + dayNamesMin[dow] + '</span>'); 1925 } 1926 return header; 1927 }, 1928 1929 /* Generate selection controls for month. 1930 @param inst (object) the current instance settings 1931 @param year (number) the year to generate 1932 @param month (number) the month to generate 1933 @param minDate (CDate) the minimum date allowed 1934 @param maxDate (CDate) the maximum date allowed 1935 @param monthHeader (string) the month/year format 1936 @return (string) the month selection content */ 1937 _generateMonthSelection: function(inst, year, month, minDate, maxDate, monthHeader) { 1938 if (!inst.get('changeMonth')) { 1939 return $.datepick.formatDate( 1940 monthHeader, $.datepick.newDate(year, month, 1), inst.getConfig()); 1941 } 1942 // Months 1943 var monthNames = inst.get('monthNames' + (monthHeader.match(/mm/i) ? '' : 'Short')); 1944 var html = monthHeader.replace(/m+/i, '\\x2E').replace(/y+/i, '\\x2F'); 1945 var selector = '<select class="' + this._monthYearClass + 1946 '" title="' + inst.get('monthStatus') + '">'; 1947 for (var m = 1; m <= 12; m++) { 1948 if ((!minDate || $.datepick.newDate(year, m, $.datepick.daysInMonth(year, m)). 1949 getTime() >= minDate.getTime()) && 1950 (!maxDate || $.datepick.newDate(year, m, 1).getTime() <= maxDate.getTime())) { 1951 selector += '<option value="' + m + '/' + year + '"' + 1952 (month == m ? ' selected="selected"' : '') + '>
1952' + 1953 monthNames[m - 1] + '</option>'; 1954 } 1955 } 1956 selector += '</select>'; 1957 html = html.replace(/\\x2E/, selector); 1958 // Years 1959 var yearRange = inst.get('yearRange'); 1960 if (yearRange == 'any') { 1961 selector = '<select class="' + this._monthYearClass + ' ' + this._anyYearClass + 1962 '" title="' + inst.get('yearStatus') + '">' + 1963 '<option>' + year + '</option></select>' + 1964 '<input class="' + this._monthYearClass + ' ' + this._curMonthClass + 1965 month + '" value="' + year + '">'; 1966 } 1967 else { 1968 yearRange = yearRange.split(':'); 1969 var todayYear = $.datepick.today().getFullYear(); 1970 var start = (yearRange[0].match('c[+-].*') ? year + parseInt(yearRange[0].substring(1), 10) : 1971 ((yearRange[0].match('[+-].*') ? todayYear : 0) + parseInt(yearRange[0], 10))); 1972 var end = (yearRange[1].match('c[+-].*') ? year + parseInt(yearRange[1].substring(1), 10) : 1973 ((yearRange[1].match('[+-].*') ? todayYear : 0) + parseInt(yearRange[1], 10))); 1974 selector = '<select class="' + this._monthYearClass + 1975 '" title="' + inst.get('yearStatus') + '">'; 1976 start = $.datepick.add($.datepick.newDate(start + 1, 1, 1), -1, 'd'); 1977 end = $.datepick.newDate(end, 1, 1); 1978 var addYear = function(y) { 1979 if (y != 0) { 1980 selector += '<option value="' + month + '/' + y + '"' + 1981 (year == y ? ' selected="selected"' : '') + '>' + y + '</option>'; 1982 } 1983 }; 1984 if (start.getTime() < end.getTime()) { 1985 start = (minDate && minDate.getTime() > start.getTime() ? minDate : start).getFullYear(); 1986 end = (maxDate && maxDate.getTime() < end.getTime() ? maxDate : end).getFullYear(); 1987 for (var y = start; y <= end; y++) { 1988 addYear(y); 1989 } 1990 } 1991 else { 1992 start = (maxDate && maxDate.getTime() < start.getTime() ? maxDate : start).getFullYear(); 1993 end = (minDate && minDate.getTime() > end.getTime() ? minDate : end).getFullYear(); 1994 for (var y = start; y >= end; y--) { 1995 addYear(y); 1996 } 1997 } 1998 selector += '</select>'; 1999 } 2000 html = html.replace(/\\x2F/, selector); 2001 return html; 2002 }, 2003 2004 /* Prepare a render template for use. 2005 Exclude popup/inline sections that are not applicable. 2006 Localise text of the form: {l10n:name}. 2007 @param text (string) the text to localise 2008 @param inst (object) the current instance settings 2009 @return (string) the localised text */ 2010 _prepare: function(text, inst) { 2011 var replaceSection = function(type, retain) { 2012 while (true) { 2013 var start = text.indexOf('{' + type + ':start}'); 2014 if (start == -1) { 2015 return; 2016 } 2017 var end = text.substring(start).indexOf('{' + type + ':end}'); 2018 if (end > -1) { 2019 text = text.substring(0, start) + 2020 (retain ? text.substr(start + type.length + 8, end - type.length - 8) : '') + 2021 text.substring(start + end + type.length + 6); 2022 } 2023 } 2024 }; 2025 replaceSection('inline', inst.inline); 2026 replaceSection('popup', !inst.inline); 2027 var pattern = /\{l10n:([^\}]+)\}/; 2028 var matches = null; 2029 while (matches = pattern.exec(text)) { 2030 text = text.replace(matches[0], inst.get(matches[1])); 2031 } 2032 return text; 2033 } 2034}); 2035 2036/* jQuery extend now ignores nulls! 2037 @param target (object) the object to extend 2038 @param props (object) the new settings 2039 @return (object) the updated object */ 2040function extendRemove(target, props) { 2041 $.extend(target, props); 2042 for (var name in props) 2043 if (props[name] == null || props[name] == undefined) 2044 target[name] = props[name]; 2045 return target; 2046}; 2047 2048/* Attach the datepicker functionality to a jQuery selection. 2049 @param command (string) the command to run (optional, default 'attach') 2050 @param options (object) the new settings to use for these instances (optional) 2051 @return (jQuery) for chaining further calls */ 2052$.fn.datepick = function(options) { 2053 var otherArgs = Array.prototype.slice.call(arguments, 1); 2054 if ($.inArray(options, ['getDate', 'isDisabled', 'isSelectable', 'options', 'retrieveDate']) > -1) { 2055 return $.datepick[options].apply($.datepick, [this[0]].concat(otherArgs)); 2056 } 2057 return this.each(function() { 2058 if (typeof options == 'string') { 2059 $.datepick[options].apply($.datepick, [this].concat(otherArgs)); 2060 } 2061 else { 2062 $.datepick._attachPicker(this, options || {}); 2063 } 2064 }); 2065}; 2066 2067/* Initialise the datepicker functionality. */ 2068$.datepick = new Datepicker(); // singleton instance 2069 2070$(function() { 2071 $(document).mousedown($.datepick._checkExternalClick). 2072 resize(function() { $.datepick.hide($.datepick.curInst); }); 2073}); 2074 2075})(typeof JQUERY_CULTUREBASE !== 'undefined' ? JQUERY_CULTUREBASE : jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.