[ Index ] |
PHP Cross Reference of vtigercrm-6.1.0 |
[Summary view] [Print] [Text view]
1 /* http://keith-wood.name/datepick.html 2 Date picker for jQuery v4.1.0. 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. */ 11 function 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 // may be a function that takes the date and returns the offset 29 monthsToStep: 1, // How many months to move when prev/next clicked 30 monthsToJump: 12, // How many months to move when large prev/next clicked 31 useMouseWheel: true, // True to use mousewheel if available, false to never use it 32 changeMonth: true, // True to change month/year via drop-down, false for navigation only 33 yearRange: 'c-10:c+10', // Range of years to show in drop-down: 'any' for direct text entry 34 // or 'start:end', where start/end are '+-nn' for relative to today 35 // or 'c+-nn' for relative to the currently selected date 36 // or 'nnnn' for an absolute year 37 shortYearCutoff: '+10', // Cutoff for two-digit year in the current century 38 showOtherMonths: false, // True to show dates from other months, false to not show them 39 selectOtherMonths: false, // True to allow selection of dates from other months too 40 defaultDate: null, // Date to show if no other selected 41 selectDefaultDate: false, // True to pre-select the default date if no other is chosen 42 minDate: null, // The minimum selectable date 43 maxDate: null, // The maximum selectable date 44 dateFormat: 'mm/dd/yyyy', // Format for dates 45 autoSize: false, // True to size the input field according to the date format 46 rangeSelect: false, // Allows for selecting a date range on one date picker 47 rangeSeparator: ' - ', // Text between two dates in a range 48 multiSelect: 0, // Maximum number of selectable dates, zero for single select 49 multiSeparator: ',', // Text between multiple dates 50 onDate: null, // Callback as a date is added to the datepicker 51 onShow: null, // Callback just before a datepicker is shown 52 onChangeMonthYear: null, // Callback when a new month/year is selected 53 onSelect: null, // Callback when a date is selected 54 onClose: null, // Callback when a datepicker is closed 55 altField: null, // Alternate field to update in synch with the datepicker 56 altFormat: null, // Date format for alternate field, defaults to dateFormat 57 constrainInput: true, // True to constrain typed input to dateFormat allowed characters 58 commandsAsDateFormat: false, // True to apply formatDate to the command texts 59 commands: this.commands // Command actions that may be added to a layout by name 60 }; 61 this.regional = []; 62 this.regional[''] = { // US/English 63 monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 64 'July', 'August', 'September', 'October', 'November', 'December'], 65 monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], 66 dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], 67 dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], 68 dayNamesMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], 69 dateFormat: 'mm/dd/yyyy', // See options on formatDate 70 firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ... 71 renderer: this.defaultRenderer, // The rendering templates 72 prevText: '<Prev', // Text for the previous month command 73 prevStatus: 'Show the previous month', // Status text for the previous month command 74 prevJumpText: '<<', // Text for the previous year command 75 prevJumpStatus: 'Show the previous year', // Status text for the previous year command 76 nextText: 'Next>', // Text for the next month command 77 nextStatus: 'Show the next month', // Status text for the next month command 78 nextJumpText: '>>', // Text for the next year command 79 nextJumpStatus: 'Show the next year', // Status text for the next year command 80 currentText: 'Current', // Text for the current month command 81 currentStatus: 'Show the current month', // Status text for the current month command 82 todayText: 'Today', // Text for the today's month command 83 todayStatus: 'Show today\'s month', // Status text for the today's month command 84 clearText: 'Clear', // Text for the clear command 85 clearStatus: 'Clear all the dates', // Status text for the clear command 86 closeText: 'Close', // Text for the close command 87 closeStatus: 'Close the datepicker', // Status text for the close command 88 yearStatus: 'Change the year', // Status text for year selection 89 monthStatus: 'Change the month', // Status text for month selection 90 weekText: 'Wk', // Text for week of the year column header 91 weekStatus: 'Week of the year', // Status text for week of the year column header 92 dayStatus: 'Select DD, M d, yyyy', // Status text for selectable days 93 defaultStatus: 'Select a date', // Status text shown by default 94 isRTL: false // True if language is right-to-left 95 }; 96 $.extend(this._defaults, this.regional['']); 97 this._disabled = []; 98 } 99 100 $.extend(Datepicker.prototype, { 101 /* Class name added to elements to indicate already configured with datepicker. */ 102 markerClassName: 'hasDatepick', 103 /* Name of the data property for instance settings. */ 104 propertyName: 'datepick', 105 106 _popupClass: 'datepick-popup', // Marker for popup division 107 _triggerClass: 'datepick-trigger', // Marker for trigger element 108 _disableClass: 'datepick-disable', // Marker for disabled 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 || plugin.add(plugin.day( 128 plugin._applyMonthsOffset(plugin.add(plugin.newDate(inst.drawDate), 129 1 - inst.options.monthsToStep, 'm'), inst), 1), -1, 'd'). 130 getTime() >= minDate.getTime()); }, 131 date: function(inst) { 132 return plugin.day(plugin._applyMonthsOffset(plugin.add( 133 plugin.newDate(inst.drawDate), -inst.options.monthsToStep, 'm'), inst), 1); }, 134 action: function(inst) { 135 plugin._changeMonthPlugin(this, -inst.options.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 || plugin.add(plugin.day( 142 plugin._applyMonthsOffset(plugin.add(plugin.newDate(inst.drawDate), 143 1 - inst.options.monthsToJump, 'm'), inst), 1), -1, 'd'). 144 getTime() >= minDate.getTime()); }, 145 date: function(inst) { 146 return plugin.day(plugin._applyMonthsOffset(plugin.add( 147 plugin.newDate(inst.drawDate), -inst.options.monthsToJump, 'm'), inst), 1); }, 148 action: function(inst) { 149 plugin._changeMonthPlugin(this, -inst.options.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 || plugin.day(plugin._applyMonthsOffset(plugin.add( 156 plugin.newDate(inst.drawDate), inst.options.monthsToStep, 'm'), inst), 1). 157 getTime() <= maxDate.getTime()); }, 158 date: function(inst) { 159 return plugin.day(plugin._applyMonthsOffset(plugin.add( 160 plugin.newDate(inst.drawDate), inst.options.monthsToStep, 'm'), inst), 1); }, 161 action: function(inst) { 162 plugin._changeMonthPlugin(this, inst.options.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 || plugin.day(plugin._applyMonthsOffset(plugin.add( 169 plugin.newDate(inst.drawDate), inst.options.monthsToJump, 'm'), inst), 1). 170 getTime() <= maxDate.getTime()); }, 171 date: function(inst) { 172 return plugin.day(plugin._applyMonthsOffset(plugin.add( 173 plugin.newDate(inst.drawDate), inst.options.monthsToJump, 'm'), inst), 1); }, 174 action: function(inst) { 175 plugin._changeMonthPlugin(this, inst.options.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] || plugin.today(); 183 return (!minDate || curDate.getTime() >= minDate.getTime()) && 184 (!maxDate || curDate.getTime() <= maxDate.getTime()); }, 185 date: function(inst) { 186 return inst.selectedDates[0] || plugin.today(); }, 187 action: function(inst) { 188 var curDate = inst.selectedDates[0] || plugin.today(); 189 plugin._showMonthPlugin(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 || plugin.today().getTime() >= minDate.getTime()) && 197 (!maxDate || plugin.today().getTime() <= maxDate.getTime()); }, 198 date: function(inst) { return plugin.today(); }, 199 action: function(inst) { plugin._showMonthPlugin(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) { plugin._clearPlugin(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) { plugin._hidePlugin(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 || plugin.add(plugin.newDate(inst.drawDate), -7, 'd'). 218 getTime() >= minDate.getTime()); }, 219 date: function(inst) { return plugin.add(plugin.newDate(inst.drawDate), -7, 'd'); }, 220 action: function(inst) { plugin._changeDayPlugin(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 || plugin.add(plugin.newDate(inst.drawDate), -1, 'd'). 227 getTime() >= minDate.getTime()); }, 228 date: function(inst) { return plugin.add(plugin.newDate(inst.drawDate), -1, 'd'); }, 229 action: function(inst) { plugin._changeDayPlugin(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 || plugin.add(plugin.newDate(inst.drawDate), 1, 'd'). 236 getTime() <= maxDate.getTime()); }, 237 date: function(inst) { return plugin.add(plugin.newDate(inst.drawDate), 1, 'd'); }, 238 action: function(inst) { plugin._changeDayPlugin(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 || plugin.add(plugin.newDate(inst.drawDate), 7, 'd'). 245 getTime() <= maxDate.getTime()); }, 246 date: function(inst) { return plugin.add(plugin.newDate(inst.drawDate), 7, 'd'); }, 247 action: function(inst) { plugin._changeDayPlugin(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 options (object) the new settings to use as defaults 310 @return (Datepicker) this object */ 311 setDefaults: function(options) { 312 $.extend(this._defaults, options || {}); 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 var isDoubled = doubled(match, step); 496 var size = [2, 3, isDoubled ? 4 : 2, 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).toLowerCase() == names[i].toLowerCase()) { 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 plugin.parseDate(dateFormat, offset, settings); 627 } 628 catch (e) { 629 // Ignore 630 } 631 offset = offset.toLowerCase(); 632 var date = (offset.match(/^c/) && currentDate ? plugin.newDate(currentDate) : null) || 633 plugin.today(); 634 var pattern = /([+-]?[0-9]+)\s*(d|w|m|y)?/g; 635 var matches = null; 636 while (matches = pattern.exec(offset)) { 637 date = plugin.add(date, parseInt(matches[1], 10), matches[2] || 'd'); 638 } 639 return date; 640 }; 641 defaultDate = (defaultDate ? plugin.newDate(defaultDate) : null); 642 dateSpec = (dateSpec == null ? defaultDate : 643 (typeof dateSpec == 'string' ? offsetString(dateSpec) : (typeof dateSpec == 'number' ? 644 (isNaN(dateSpec) || dateSpec == Infinity || dateSpec == -Infinity ? defaultDate : 645 plugin.add(plugin.today(), dateSpec, 'd')) : plugin.newDate(dateSpec)))); 646 return dateSpec; 647 }, 648 649 /* Find the number of days in a given month. 650 @param year (Date) the date to get days for or 651 (number) the full year 652 @param month (number) the month (1 to 12) 653 @return (number) the number of days in this month */ 654 daysInMonth: function(year, month) { 655 month = (year.getFullYear ? year.getMonth() + 1 : month); 656 year = (year.getFullYear ? year.getFullYear() : year); 657 return this.newDate(year, month + 1, 0).getDate(); 658 }, 659 660 /* Calculate the day of the year for a date. 661 @param year (Date) the date to get the day-of-year for or 662 (number) the full year 663 @param month (number) the month (1-12) 664 @param day (number) the day 665 @return (number) the day of the year */ 666 dayOfYear: function(year, month, day) { 667 var date = (year.getFullYear ? year : this.newDate(year, month, day)); 668 var newYear = this.newDate(date.getFullYear(), 1, 1); 669 return Math.floor((date.getTime() - newYear.getTime()) / this._msPerDay) + 1; 670 }, 671 672 /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. 673 @param year (Date) the date to get the week for or 674 (number) the full year 675 @param month (number) the month (1-12) 676 @param day (number) the day 677 @return (number) the number of the week within the year that contains this date */ 678 iso8601Week: function(year, month, day) { 679 var checkDate = (year.getFullYear ? 680 new Date(year.getTime()) : this.newDate(year, month, day)); 681 // Find Thursday of this week starting on Monday 682 checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); 683 var time = checkDate.getTime(); 684 checkDate.setMonth(0, 1); // Compare with Jan 1 685 return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; 686 }, 687 688 /* Return today's date. 689 @return (Date) today */ 690 today: function() { 691 return this._normaliseDate(new Date()); 692 }, 693 694 /* Return a new date. 695 @param year (Date) the date to clone or 696 (number) the year 697 @param month (number) the month (1-12) 698 @param day (number) the day 699 @return (Date) the date */ 700 newDate: function(year, month, day) { 701 return (!year ? null : (year.getFullYear ? this._normaliseDate(new Date(year.getTime())) : 702 new Date(year, month - 1, day, 12))); 703 }, 704 705 /* Standardise a date into a common format - time portion is 12 noon. 706 @param date (Date) the date to standardise 707 @return (Date) the normalised date */ 708 _normaliseDate: function(date) { 709 if (date) { 710 date.setHours(12, 0, 0, 0); 711 } 712 return date; 713 }, 714 715 /* Set the year for a date. 716 @param date (Date) the original date 717 @param year (number) the new year 718 @return the updated date */ 719 year: function(date, year) { 720 date.setFullYear(year); 721 return this._normaliseDate(date); 722 }, 723 724 /* Set the month for a date. 725 @param date (Date) the original date 726 @param month (number) the new month (1-12) 727 @return the updated date */ 728 month: function(date, month) { 729 date.setMonth(month - 1); 730 return this._normaliseDate(date); 731 }, 732 733 /* Set the day for a date. 734 @param date (Date) the original date 735 @param day (number) the new day of the month 736 @return the updated date */ 737 day: function(date, day) { 738 date.setDate(day); 739 return this._normaliseDate(date); 740 }, 741 742 /* Add a number of periods to a date. 743 @param date (Date) the original date 744 @param amount (number) the number of periods 745 @param period (string) the type of period d/w/m/y 746 @return the updated date */ 747 add: function(date, amount, period) { 748 if (period == 'd' || period == 'w') { 749 this._normaliseDate(date); 750 date.setDate(date.getDate() + amount * (period == 'w' ? 7 : 1)); 751 } 752 else { 753 var year = date.getFullYear() + (period == 'y' ? amount : 0); 754 var month = date.getMonth() + (period == 'm' ? amount : 0); 755 date.setTime(plugin.newDate(year, month + 1, 756 Math.min(date.getDate(), this.daysInMonth(year, month + 1))).getTime()); 757 } 758 return date; 759 }, 760 761 /* Apply the months offset value to a date. 762 @param date (Date) the original date 763 @param inst (object) the current instance settings 764 @return (Date) the updated date */ 765 _applyMonthsOffset: function(date, inst) { 766 var monthsOffset = inst.options.monthsOffset; 767 if ($.isFunction(monthsOffset)) { 768 monthsOffset = monthsOffset.apply(inst.target[0], [date]); 769 } 770 return plugin.add(date, -monthsOffset, 'm'); 771 }, 772 773 /* Attach the datepicker functionality to an input field. 774 @param target (element) the control to affect 775 @param options (object) the custom options for this instance */ 776 _attachPlugin: function(target, options) { 777 target = $(target); 778 if (target.hasClass(this.markerClassName)) { 779 return; 780 } 781 var inlineSettings = ($.fn.metadata ? target.metadata() : {}); 782 var inst = {options: $.extend({}, this._defaults, inlineSettings, options), 783 target: target, selectedDates: [], drawDate: null, pickingRange: false, 784 inline: ($.inArray(target[0].nodeName.toLowerCase(), ['div', 'span']) > -1), 785 get: function(name) { // Get a setting value, computing if necessary 786 if ($.inArray(name, ['defaultDate', 'minDate', 'maxDate']) > -1) { // Decode date settings 787 return plugin.determineDate(this.options[name], null, 788 this.selectedDates[0], this.options.dateFormat, inst.getConfig()); 789 } 790 return this.options[name]; 791 }, 792 curMinDate: function() { 793 return (this.pickingRange ? this.selectedDates[0] : this.get('minDate')); 794 }, 795 getConfig: function() { 796 return {dayNamesShort: this.options.dayNamesShort, dayNames: this.options.dayNames, 797 monthNamesShort: this.options.monthNamesShort, monthNames: this.options.monthNames, 798 calculateWeek: this.options.calculateWeek, 799 shortYearCutoff: this.options.shortYearCutoff}; 800 } 801 }; 802 target.addClass(this.markerClassName).data(this.propertyName, inst); 803 if (inst.inline) { 804 inst.drawDate = plugin._checkMinMax(plugin.newDate(inst.selectedDates[0] || 805 inst.get('defaultDate') || plugin.today()), inst); 806 inst.prevDate = plugin.newDate(inst.drawDate); 807 this._update(target[0]); 808 if ($.fn.mousewheel) { 809 target.mousewheel(this._doMouseWheel); 810 } 811 } 812 else { 813 this._attachments(target, inst); 814 target.bind('keydown.' + this.propertyName, this._keyDown). 815 bind('keypress.' + this.propertyName, this._keyPress). 816 bind('keyup.' + this.propertyName, this._keyUp); 817 if (target.attr('disabled')) { 818 this._disablePlugin(target[0]); 819 } 820 } 821 }, 822 823 /* Retrieve or reconfigure the settings for a control. 824 @param target (element) the control to affect 825 @param options (object) the new options for this instance or 826 (string) an individual property name 827 @param value (any) the individual property value (omit if options 828 is an object or to retrieve the value of a setting) 829 @return (any) if retrieving a value */ 830 _optionPlugin: function(target, options, value) { 831 target = $(target); 832 var inst = target.data(this.propertyName); 833 if (!options || (typeof options == 'string' && value == null)) { // Get option 834 var name = options; 835 options = (inst || {}).options; 836 return (options && name ? options[name] : options); 837 } 838 839 if (!target.hasClass(this.markerClassName)) { 840 return; 841 } 842 options = options || {}; 843 if (typeof options == 'string') { 844 var name = options; 845 options = {}; 846 options[name] = value; 847 } 848 if (options.calendar && options.calendar != inst.options.calendar) { 849 var discardDate = function(name) { 850 return (typeof inst.options[name] == 'object' ? null : inst.options[name]); 851 }; 852 options = $.extend({defaultDate: discardDate('defaultDate'), 853 minDate: discardDate('minDate'), maxDate: discardDate('maxDate')}, options); 854 inst.selectedDates = []; 855 inst.drawDate = null; 856 } 857 var dates = inst.selectedDates; 858 $.extend(inst.options, options); 859 this._setDatePlugin(target[0], dates, null, false, true); 860 inst.pickingRange = false; 861 inst.drawDate = plugin.newDate(this._checkMinMax( 862 (inst.options.defaultDate ? inst.get('defaultDate') : inst.drawDate) || 863 inst.get('defaultDate') || plugin.today(), inst)); 864 if (!inst.inline) { 865 this._attachments(target, inst); 866 } 867 if (inst.inline || inst.div) { 868 this._update(target[0]); 869 } 870 }, 871 872 /* Attach events and trigger, if necessary. 873 @param target (jQuery) the control to affect 874 @param inst (object) the current instance settings */ 875 _attachments: function(target, inst) { 876 target.unbind('focus.' + this.propertyName); 877 if (inst.options.showOnFocus) { 878 target.bind('focus.' + this.propertyName, this._showPlugin); 879 } 880 if (inst.trigger) { 881 inst.trigger.remove(); 882 } 883 var trigger = inst.options.showTrigger; 884 inst.trigger = (!trigger ? $([]) : 885 $(trigger).clone().removeAttr('id').addClass(this._triggerClass) 886 [inst.options.isRTL ? 'insertBefore' : 'insertAfter'](target). 887 click(function() { 888 if (!plugin._isDisabledPlugin(target[0])) { 889 plugin[plugin.curInst == inst ? '_hidePlugin' : '_showPlugin'](target[0]); 890 } 891 })); 892 this._autoSize(target, inst); 893 var dates = this._extractDates(inst, target.val()); 894 if (dates) { 895 this._setDatePlugin(target[0], dates, null, true); 896 } 897 var defaultDate = inst.get('defaultDate'); 898 if (inst.options.selectDefaultDate && defaultDate && inst.selectedDates.length == 0) { 899 this._setDatePlugin(target[0], plugin.newDate(defaultDate || plugin.today())); 900 } 901 }, 902 903 /* Apply the maximum length for the date format. 904 @param inst (object) the current instance settings */ 905 _autoSize: function(target, inst) { 906 if (inst.options.autoSize && !inst.inline) { 907 var date = plugin.newDate(2009, 10, 20); // Ensure double digits 908 var dateFormat = inst.options.dateFormat; 909 if (dateFormat.match(/[DM]/)) { 910 var findMax = function(names) { 911 var max = 0; 912 var maxI = 0; 913 for (var i = 0; i < names.length; i++) { 914 if (names[i].length > max) { 915 max = names[i].length; 916 maxI = i; 917 } 918 } 919 return maxI; 920 }; 921 date.setMonth(findMax(inst.options[dateFormat.match(/MM/) ? // Longest month 922 'monthNames' : 'monthNamesShort'])); 923 date.setDate(findMax(inst.options[dateFormat.match(/DD/) ? // Longest day 924 'dayNames' : 'dayNamesShort']) + 20 - date.getDay()); 925 } 926 inst.target.attr('size', plugin.formatDate(dateFormat, date, inst.getConfig()).length); 927 } 928 }, 929 930 /* Remove the datepicker functionality from a control. 931 @param target (element) the control to affect */ 932 _destroyPlugin: function(target) { 933 target = $(target); 934 if (!target.hasClass(this.markerClassName)) { 935 return; 936 } 937 var inst = target.data(this.propertyName); 938 if (inst.trigger) { 939 inst.trigger.remove(); 940 } 941 target.removeClass(this.markerClassName).removeData(this.propertyName). 942 empty().unbind('.' + this.propertyName); 943 if (inst.inline && $.fn.mousewheel) { 944 target.unmousewheel(); 945 } 946 if (!inst.inline && inst.options.autoSize) { 947 target.removeAttr('size'); 948 } 949 }, 950 951 /* Apply multiple event functions. 952 Usage, for example: onShow: multipleEvents(fn1, fn2, ...) 953 @param fns (function...) the functions to apply */ 954 multipleEvents: function(fns) { 955 var funcs = arguments; 956 return function(args) { 957 for (var i = 0; i < funcs.length; i++) { 958 funcs[i].apply(this, arguments); 959 } 960 }; 961 }, 962 963 /* Enable the control. 964 @param target (element) the control to affect */ 965 _enablePlugin: function(target) { 966 target = $(target); 967 if (!target.hasClass(this.markerClassName)) { 968 return; 969 } 970 var inst = target.data(this.propertyName); 971 if (inst.inline) { 972 target.children('.' + this._disableClass).remove().end(). 973 find('button,select').removeAttr('disabled').end(). 974 find('a').attr('href', 'javascript:void(0)'); 975 } 976 else { 977 target.prop('disabled', false); 978 inst.trigger.filter('button.' + this._triggerClass). 979 removeAttr('disabled').end(). 980 filter('img.' + this._triggerClass). 981 css({opacity: '1.0', cursor: ''}); 982 } 983 this._disabled = $.map(this._disabled, 984 function(value) { return (value == target[0] ? null : value); }); // Delete entry 985 }, 986 987 /* Disable the control. 988 @param target (element) the control to affect */ 989 _disablePlugin: function(target) { 990 target = $(target); 991 if (!target.hasClass(this.markerClassName)) { 992 return; 993 } 994 var inst = target.data(this.propertyName); 995 if (inst.inline) { 996 var inline = target.children(':last'); 997 var offset = inline.offset(); 998 var relOffset = {left: 0, top: 0}; 999 inline.parents().each(function() { 1000 if ($(this).css('position') == 'relative') { 1001 relOffset = $(this).offset(); 1002 return false; 1003 } 1004 }); 1005 var zIndex = target.css('zIndex'); 1006 zIndex = (zIndex == 'auto' ? 0 : parseInt(zIndex, 10)) + 1; 1007 target.prepend('<div class="' + this._disableClass + '" style="' + 1008 'width: ' + inline.outerWidth() + 'px; height: ' + inline.outerHeight() + 1009 'px; left: ' + (offset.left - relOffset.left) + 'px; top: ' + 1010 (offset.top - relOffset.top) + 'px; z-index: ' + zIndex + '"></div>'). 1011 find('button,select').attr('disabled', 'disabled').end(). 1012 find('a').removeAttr('href'); 1013 } 1014 else { 1015 target.prop('disabled', true); 1016 inst.trigger.filter('button.' + this._triggerClass). 1017 attr('disabled', 'disabled').end(). 1018 filter('img.' + this._triggerClass). 1019 css({opacity: '0.5', cursor: 'default'}); 1020 } 1021 this._disabled = $.map(this._disabled, 1022 function(value) { return (value == target[0] ? null : value); }); // Delete entry 1023 this._disabled.push(target[0]); 1024 }, 1025 1026 /* Is the first field in a jQuery collection disabled as a datepicker? 1027 @param target (element) the control to examine 1028 @return (boolean) true if disabled, false if enabled */ 1029 _isDisabledPlugin: function(target) { 1030 return (target && $.inArray(target, this._disabled) > -1); 1031 }, 1032 1033 /* Show a popup datepicker. 1034 @param target (event) a focus event or 1035 (element) the control to use */ 1036 _showPlugin: function(target) { 1037 target = $(target.target || target); 1038 var inst = target.data(plugin.propertyName); 1039 if (plugin.curInst == inst) { 1040 return; 1041 } 1042 if (plugin.curInst) { 1043 plugin._hidePlugin(plugin.curInst, true); 1044 } 1045 if (inst) { 1046 // Retrieve existing date(s) 1047 inst.lastVal = null; 1048 inst.selectedDates = plugin._extractDates(inst, target.val()); 1049 inst.pickingRange = false; 1050 inst.drawDate = plugin._checkMinMax(plugin.newDate(inst.selectedDates[0] || 1051 inst.get('defaultDate') || plugin.today()), inst); 1052 inst.prevDate = plugin.newDate(inst.drawDate); 1053 plugin.curInst = inst; 1054 // Generate content 1055 plugin._update(target[0], true); 1056 // Adjust position before showing 1057 var offset = plugin._checkOffset(inst); 1058 inst.div.css({left: offset.left, top: offset.top}); 1059 // And display 1060 var showAnim = inst.options.showAnim; 1061 var showSpeed = inst.options.showSpeed; 1062 showSpeed = (showSpeed == 'normal' && $.ui && $.ui.version >= '1.8' ? 1063 '_default' : showSpeed); 1064 if ($.effects && $.effects[showAnim]) { 1065 var data = inst.div.data(); // Update old effects data 1066 for (var key in data) { 1067 if (key.match(/^ec\.storage\./)) { 1068 data[key] = inst._mainDiv.css(key.replace(/ec\.storage\./, '')); 1069 } 1070 } 1071 inst.div.data(data).show(showAnim, inst.options.showOptions, showSpeed); 1072 } 1073 else { 1074 inst.div[showAnim || 'show']((showAnim ? showSpeed : '')); 1075 } 1076 } 1077 }, 1078 1079 /* Extract possible dates from a string. 1080 @param inst (object) the current instance settings 1081 @param text (string) the text to extract from 1082 @return (CDate[]) the extracted dates */ 1083 _extractDates: function(inst, datesText) { 1084 if (datesText == inst.lastVal) { 1085 return; 1086 } 1087 inst.lastVal = datesText; 1088 datesText = datesText.split(inst.options.multiSelect ? inst.options.multiSeparator : 1089 (inst.options.rangeSelect ? inst.options.rangeSeparator : '\x00')); 1090 var dates = []; 1091 for (var i = 0; i < datesText.length; i++) { 1092 try { 1093 var date = plugin.parseDate(inst.options.dateFormat, datesText[i], inst.getConfig()); 1094 if (date) { 1095 var found = false; 1096 for (var j = 0; j < dates.length; j++) { 1097 if (dates[j].getTime() == date.getTime()) { 1098 found = true; 1099 break; 1100 } 1101 } 1102 if (!found) { 1103 dates.push(date); 1104 } 1105 } 1106 } 1107 catch (e) { 1108 // Ignore 1109 } 1110 } 1111 dates.splice(inst.options.multiSelect || (inst.options.rangeSelect ? 2 : 1), dates.length); 1112 if (inst.options.rangeSelect && dates.length == 1) { 1113 dates[1] = dates[0]; 1114 } 1115 return dates; 1116 }, 1117 1118 /* Update the datepicker display. 1119 @param target (event) a focus event or 1120 (element) the control to use 1121 @param hidden (boolean) true to initially hide the datepicker */ 1122 _update: function(target, hidden) { 1123 target = $(target.target || target); 1124 var inst = target.data(plugin.propertyName); 1125 if (inst) { 1126 if (inst.inline || plugin.curInst == inst) { 1127 if ($.isFunction(inst.options.onChangeMonthYear) && (!inst.prevDate || 1128 inst.prevDate.getFullYear() != inst.drawDate.getFullYear() || 1129 inst.prevDate.getMonth() != inst.drawDate.getMonth())) { 1130 inst.options.onChangeMonthYear.apply(target[0], 1131 [inst.drawDate.getFullYear(), inst.drawDate.getMonth() + 1]); 1132 } 1133 } 1134 if (inst.inline) { 1135 target.html(this._generateContent(target[0], inst)); 1136 } 1137 else if (plugin.curInst == inst) { 1138 if (!inst.div) { 1139 inst.div = $('<div></div>').addClass(this._popupClass). 1140 css({display: (hidden ? 'none' : 'static'), position: 'absolute', 1141 left: target.offset().left, 1142 top: target.offset().top + target.outerHeight()}). 1143 appendTo($(inst.options.popupContainer || 'body')); 1144 if ($.fn.mousewheel) { 1145 inst.div.mousewheel(this._doMouseWheel); 1146 } 1147 } 1148 inst.div.html(this._generateContent(target[0], inst)); 1149 target.focus(); 1150 } 1151 } 1152 }, 1153 1154 /* Update the input field and any alternate field with the current dates. 1155 @param target (element) the control to use 1156 @param keyUp (boolean, internal) true if coming from keyUp processing */ 1157 _updateInput: function(target, keyUp) { 1158 var inst = $.data(target, this.propertyName); 1159 if (inst) { 1160 var value = ''; 1161 var altValue = ''; 1162 var sep = (inst.options.multiSelect ? inst.options.multiSeparator : 1163 inst.options.rangeSeparator); 1164 var altFormat = inst.options.altFormat || inst.options.dateFormat; 1165 for (var i = 0; i < inst.selectedDates.length; i++) { 1166 value += (keyUp ? '' : (i > 0 ? sep : '') + plugin.formatDate( 1167 inst.options.dateFormat, inst.selectedDates[i], inst.getConfig())); 1168 altValue += (i > 0 ? sep : '') + plugin.formatDate( 1169 altFormat, inst.selectedDates[i], inst.getConfig()); 1170 } 1171 if (!inst.inline && !keyUp) { 1172 $(target).val(value); 1173 } 1174 $(inst.options.altField).val(altValue); 1175 if ($.isFunction(inst.options.onSelect) && !keyUp && !inst.inSelect) { 1176 inst.inSelect = true; // Prevent endless loops 1177 inst.options.onSelect.apply(target, [inst.selectedDates]); 1178 inst.inSelect = false; 1179 } 1180 } 1181 }, 1182 1183 /* Retrieve the size of left and top borders for an element. 1184 @param elem (jQuery) the element of interest 1185 @return (number[2]) the left and top borders */ 1186 _getBorders: function(elem) { 1187 var convert = function(value) { 1188 return {thin: 1, medium: 3, thick: 5}[value] || value; 1189 }; 1190 return [parseFloat(convert(elem.css('border-left-width'))), 1191 parseFloat(convert(elem.css('border-top-width')))]; 1192 }, 1193 1194 /* Check positioning to remain on the screen. 1195 @param inst (object) the current instance settings 1196 @return (object) the updated offset for the datepicker */ 1197 _checkOffset: function(inst) { 1198 var base = (inst.target.is(':hidden') && inst.trigger ? inst.trigger : inst.target); 1199 var offset = base.offset(); 1200 var browserWidth = $(window).width(); 1201 var browserHeight = $(window).height(); 1202 if (browserWidth == 0) { 1203 return offset; 1204 } 1205 var isFixed = false; 1206 $(inst.target).parents().each(function() { 1207 isFixed |= $(this).css('position') == 'fixed'; 1208 return !isFixed; 1209 }); 1210 var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; 1211 var scrollY = document.documentElement.scrollTop || document.body.scrollTop; 1212 var above = offset.top - (isFixed ? scrollY : 0) - inst.div.outerHeight(); 1213 var below = offset.top - (isFixed ? scrollY : 0) + base.outerHeight(); 1214 var alignL = offset.left - (isFixed ? scrollX : 0); 1215 var alignR = offset.left - (isFixed ? scrollX : 0) + base.outerWidth() - inst.div.outerWidth(); 1216 var tooWide = (offset.left - scrollX + inst.div.outerWidth()) > browserWidth; 1217 var tooHigh = (offset.top - scrollY + inst.target.outerHeight() + 1218 inst.div.outerHeight()) > browserHeight; 1219 inst.div.css('position', isFixed ? 'fixed' : 'absolute'); 1220 var alignment = inst.options.alignment; 1221 if (alignment == 'topLeft') { 1222 offset = {left: alignL, top: above}; 1223 } 1224 else if (alignment == 'topRight') { 1225 offset = {left: alignR, top: above}; 1226 } 1227 else if (alignment == 'bottomLeft') { 1228 offset = {left: alignL, top: below}; 1229 } 1230 else if (alignment == 'bottomRight') { 1231 offset = {left: alignR, top: below}; 1232 } 1233 else if (alignment == 'top') { 1234 offset = {left: (inst.options.isRTL || tooWide ? alignR : alignL), top: above}; 1235 } 1236 else { // bottom 1237 offset = {left: (inst.options.isRTL || tooWide ? alignR : alignL), 1238 top: (tooHigh ? above : below)}; 1239 } 1240 offset.left = Math.max((isFixed ? 0 : scrollX), offset.left); 1241 offset.top = Math.max((isFixed ? 0 : scrollY), offset.top); 1242 return offset; 1243 }, 1244 1245 /* Close date picker if clicked elsewhere. 1246 @param event (MouseEvent) the mouse click to check */ 1247 _checkExternalClick: function(event) { 1248 if (!plugin.curInst) { 1249 return; 1250 } 1251 var target = $(event.target); 1252 if (!target.parents().andSelf().hasClass(plugin._popupClass) && 1253 !target.hasClass(plugin.markerClassName) && 1254 !target.parents().andSelf().hasClass(plugin._triggerClass)) { 1255 plugin._hidePlugin(plugin.curInst); 1256 } 1257 }, 1258 1259 /* Hide a popup datepicker. 1260 @param target (element) the control to use or 1261 (object) the current instance settings 1262 @param immediate (boolean) true to close immediately without animation */ 1263 _hidePlugin: function(target, immediate) { 1264 if (!target) { 1265 return; 1266 } 1267 var inst = $.data(target, this.propertyName) || target; 1268 if (inst && inst == plugin.curInst) { 1269 var showAnim = (immediate ? '' : inst.options.showAnim); 1270 var showSpeed = inst.options.showSpeed; 1271 showSpeed = (showSpeed == 'normal' && $.ui && $.ui.version >= '1.8' ? 1272 '_default' : showSpeed); 1273 var postProcess = function() { 1274 if (!inst.div) { 1275 return; 1276 } 1277 inst.div.remove(); 1278 inst.div = null; 1279 plugin.curInst = null; 1280 if ($.isFunction(inst.options.onClose)) { 1281 inst.options.onClose.apply(target, [inst.selectedDates]); 1282 } 1283 }; 1284 inst.div.stop(); 1285 if ($.effects && $.effects[showAnim]) { 1286 inst.div.hide(showAnim, inst.options.showOptions, showSpeed, postProcess); 1287 } 1288 else { 1289 var hideAnim = (showAnim == 'slideDown' ? 'slideUp' : 1290 (showAnim == 'fadeIn' ? 'fadeOut' : 'hide')); 1291 inst.div[hideAnim]((showAnim ? showSpeed : ''), postProcess); 1292 } 1293 if (!showAnim) { 1294 postProcess(); 1295 } 1296 } 1297 }, 1298 1299 /* Handle keystrokes in the datepicker. 1300 @param event (KeyEvent) the keystroke 1301 @return (boolean) true if not handled, false if handled */ 1302 _keyDown: function(event) { 1303 var target = event.target; 1304 var inst = $.data(target, plugin.propertyName); 1305 var handled = false; 1306 if (inst.div) { 1307 if (event.keyCode == 9) { // Tab - close 1308 plugin._hidePlugin(target); 1309 } 1310 else if (event.keyCode == 13) { // Enter - select 1311 plugin._selectDatePlugin(target, 1312 $('a.' + inst.options.renderer.highlightedClass, inst.div)[0]); 1313 handled = true; 1314 } 1315 else { // Command keystrokes 1316 var commands = inst.options.commands; 1317 for (var name in commands) { 1318 var command = commands[name]; 1319 if (command.keystroke.keyCode == event.keyCode && 1320 !!command.keystroke.ctrlKey == !!(event.ctrlKey || event.metaKey) && 1321 !!command.keystroke.altKey == event.altKey && 1322 !!command.keystroke.shiftKey == event.shiftKey) { 1323 plugin._performActionPlugin(target, name); 1324 handled = true; 1325 break; 1326 } 1327 } 1328 } 1329 } 1330 else { // Show on 'current' keystroke 1331 var command = inst.options.commands.current; 1332 if (command.keystroke.keyCode == event.keyCode && 1333 !!command.keystroke.ctrlKey == !!(event.ctrlKey || event.metaKey) && 1334 !!command.keystroke.altKey == event.altKey && 1335 !!command.keystroke.shiftKey == event.shiftKey) { 1336 plugin._showPlugin(target); 1337 handled = true; 1338 } 1339 } 1340 inst.ctrlKey = ((event.keyCode < 48 && event.keyCode != 32) || 1341 event.ctrlKey || event.metaKey); 1342 if (handled) { 1343 event.preventDefault(); 1344 event.stopPropagation(); 1345 } 1346 return !handled; 1347 }, 1348 1349 /* Filter keystrokes in the datepicker. 1350 @param event (KeyEvent) the keystroke 1351 @return (boolean) true if allowed, false if not allowed */ 1352 _keyPress: function(event) { 1353 var inst = $.data(event.target, plugin.propertyName); 1354 if (inst && inst.options.constrainInput) { 1355 var ch = String.fromCharCode(event.keyCode || event.charCode); 1356 var allowedChars = plugin._allowedChars(inst); 1357 return (event.metaKey || inst.ctrlKey || ch < ' ' || 1358 !allowedChars || allowedChars.indexOf(ch) > -1); 1359 } 1360 return true; 1361 }, 1362 1363 /* Determine the set of characters allowed by the date format. 1364 @param inst (object) the current instance settings 1365 @return (string) the set of allowed characters, or null if anything allowed */ 1366 _allowedChars: function(inst) { 1367 var allowedChars = (inst.options.multiSelect ? inst.options.multiSeparator : 1368 (inst.options.rangeSelect ? inst.options.rangeSeparator : '')); 1369 var literal = false; 1370 var hasNum = false; 1371 var dateFormat = inst.options.dateFormat; 1372 for (var i = 0; i < dateFormat.length; i++) { 1373 var ch = dateFormat.charAt(i); 1374 if (literal) { 1375 if (ch == "'" && dateFormat.charAt(i + 1) != "'") { 1376 literal = false; 1377 } 1378 else { 1379 allowedChars += ch; 1380 } 1381 } 1382 else { 1383 switch (ch) { 1384 case 'd': case 'm': case 'o': case 'w': 1385 allowedChars += (hasNum ? '' : '0123456789'); hasNum = true; break; 1386 case 'y': case '@': case '!': 1387 allowedChars += (hasNum ? '' : '0123456789') + '-'; hasNum = true; break; 1388 case 'J': 1389 allowedChars += (hasNum ? '' : '0123456789') + '-.'; hasNum = true; break; 1390 case 'D': case 'M': case 'Y': 1391 return null; // Accept anything 1392 case "'": 1393 if (dateFormat.charAt(i + 1) == "'") { 1394 allowedChars += "'"; 1395 } 1396 else { 1397 literal = true; 1398 } 1399 break; 1400 default: 1401 allowedChars += ch; 1402 } 1403 } 1404 } 1405 return allowedChars; 1406 }, 1407 1408 /* Synchronise datepicker with the field. 1409 @param event (KeyEvent) the keystroke 1410 @return (boolean) true if allowed, false if not allowed */ 1411 _keyUp: function(event) { 1412 var target = event.target; 1413 var inst = $.data(target, plugin.propertyName); 1414 if (inst && !inst.ctrlKey && inst.lastVal != inst.target.val()) { 1415 try { 1416 var dates = plugin._extractDates(inst, inst.target.val()); 1417 if (dates.length > 0) { 1418 plugin._setDatePlugin(target, dates, null, true); 1419 } 1420 } 1421 catch (event) { 1422 // Ignore 1423 } 1424 } 1425 return true; 1426 }, 1427 1428 /* Increment/decrement month/year on mouse wheel activity. 1429 @param event (event) the mouse wheel event 1430 @param delta (number) the amount of change */ 1431 _doMouseWheel: function(event, delta) { 1432 var target = (plugin.curInst && plugin.curInst.target[0]) || 1433 $(event.target).closest('.' + plugin.markerClassName)[0]; 1434 if (plugin._isDisabledPlugin(target)) { 1435 return; 1436 } 1437 var inst = $.data(target, plugin.propertyName); 1438 if (inst.options.useMouseWheel) { 1439 delta = (delta < 0 ? -1 : +1); 1440 plugin._changeMonthPlugin(target, 1441 -inst.options[event.ctrlKey ? 'monthsToJump' : 'monthsToStep'] * delta); 1442 } 1443 event.preventDefault(); 1444 }, 1445 1446 /* Clear an input and close a popup datepicker. 1447 @param target (element) the control to use */ 1448 _clearPlugin: function(target) { 1449 var inst = $.data(target, this.propertyName); 1450 if (inst) { 1451 inst.selectedDates = []; 1452 this._hidePlugin(target); 1453 var defaultDate = inst.get('defaultDate'); 1454 if (inst.options.selectDefaultDate && defaultDate) { 1455 this._setDatePlugin(target, plugin.newDate(defaultDate || plugin.today())); 1456 } 1457 else { 1458 this._updateInput(target); 1459 } 1460 } 1461 }, 1462 1463 /* Retrieve the selected date(s) for a datepicker. 1464 @param target (element) the control to examine 1465 @return (CDate[]) the selected date(s) */ 1466 _getDatePlugin: function(target) { 1467 var inst = $.data(target, this.propertyName); 1468 return (inst ? inst.selectedDates : []); 1469 }, 1470 1471 /* Set the selected date(s) for a datepicker. 1472 @param target (element) the control to examine 1473 @param dates (CDate or number or string or [] of these) the selected date(s) 1474 @param endDate (CDate or number or string) the ending date for a range (optional) 1475 @param keyUp (boolean, internal) true if coming from keyUp processing 1476 @param setOpt (boolean, internal) true if coming from option processing */ 1477 _setDatePlugin: function(target, dates, endDate, keyUp, setOpt) { 1478 var inst = $.data(target, this.propertyName); 1479 if (inst) { 1480 if (!$.isArray(dates)) { 1481 dates = [dates]; 1482 if (endDate) { 1483 dates.push(endDate); 1484 } 1485 } 1486 var minDate = inst.get('minDate'); 1487 var maxDate = inst.get('maxDate'); 1488 var curDate = inst.selectedDates[0]; 1489 inst.selectedDates = []; 1490 for (var i = 0; i < dates.length; i++) { 1491 var date = plugin.determineDate( 1492 dates[i], null, curDate, inst.options.dateFormat, inst.getConfig()); 1493 if (date) { 1494 if ((!minDate || date.getTime() >= minDate.getTime()) && 1495 (!maxDate || date.getTime() <= maxDate.getTime())) { 1496 var found = false; 1497 for (var j = 0; j < inst.selectedDates.length; j++) { 1498 if (inst.selectedDates[j].getTime() == date.getTime()) { 1499 found = true; 1500 break; 1501 } 1502 } 1503 if (!found) { 1504 inst.selectedDates.push(date); 1505 } 1506 } 1507 } 1508 } 1509 inst.selectedDates.splice(inst.options.multiSelect || 1510 (inst.options.rangeSelect ? 2 : 1), inst.selectedDates.length); 1511 if (inst.options.rangeSelect) { 1512 switch (inst.selectedDates.length) { 1513 case 1: inst.selectedDates[1] = inst.selectedDates[0]; break; 1514 case 2: inst.selectedDates[1] = 1515 (inst.selectedDates[0].getTime() > inst.selectedDates[1].getTime() ? 1516 inst.selectedDates[0] : inst.selectedDates[1]); break; 1517 } 1518 inst.pickingRange = false; 1519 } 1520 inst.prevDate = (inst.drawDate ? plugin.newDate(inst.drawDate) : null); 1521 inst.drawDate = this._checkMinMax(plugin.newDate(inst.selectedDates[0] || 1522 inst.get('defaultDate') || plugin.today()), inst); 1523 if (!setOpt) { 1524 this._update(target); 1525 this._updateInput(target, keyUp); 1526 } 1527 } 1528 }, 1529 1530 /* Determine whether a date is selectable for this datepicker. 1531 @param target (element) the control to check 1532 @param date (Date or string or number) the date to check 1533 @return (boolean) true if selectable, false if not */ 1534 _isSelectablePlugin: function(target, date) { 1535 var inst = $.data(target, this.propertyName); 1536 if (!inst) { 1537 return false; 1538 } 1539 date = plugin.determineDate(date, inst.selectedDates[0] || this.today(), null, 1540 inst.options.dateFormat, inst.getConfig()); 1541 return this._isSelectable(target, date, inst.options.onDate, 1542 inst.get('minDate'), inst.get('maxDate')); 1543 }, 1544 1545 /* Internally determine whether a date is selectable for this datepicker. 1546 @param target (element) the control to check 1547 @param date (Date) the date to check 1548 @param onDate (function or boolean) any onDate callback or callback.selectable 1549 @param mindate (Date) the minimum allowed date 1550 @param maxdate (Date) the maximum allowed date 1551 @return (boolean) true if selectable, false if not */ 1552 _isSelectable: function(target, date, onDate, minDate, maxDate) { 1553 var dateInfo = (typeof onDate == 'boolean' ? {selectable: onDate} : 1554 (!$.isFunction(onDate) ? {} : onDate.apply(target, [date, true]))); 1555 return (dateInfo.selectable != false) && 1556 (!minDate || date.getTime() >= minDate.getTime()) && 1557 (!maxDate || date.getTime() <= maxDate.getTime()); 1558 }, 1559 1560 /* Perform a named action for a datepicker. 1561 @param target (element) the control to affect 1562 @param action (string) the name of the action */ 1563 _performActionPlugin: function(target, action) { 1564 var inst = $.data(target, this.propertyName); 1565 if (inst && !this._isDisabledPlugin(target)) { 1566 var commands = inst.options.commands; 1567 if (commands[action] && commands[action].enabled.apply(target, [inst])) { 1568 commands[action].action.apply(target, [inst]); 1569 } 1570 } 1571 }, 1572 1573 /* Set the currently shown month, defaulting to today's. 1574 @param target (element) the control to affect 1575 @param year (number) the year to show (optional) 1576 @param month (number) the month to show (1-12) (optional) 1577 @param day (number) the day to show (optional) */ 1578 _showMonthPlugin: function(target, year, month, day) { 1579 var inst = $.data(target, this.propertyName); 1580 if (inst && (day != null || 1581 (inst.drawDate.getFullYear() != year || inst.drawDate.getMonth() + 1 != month))) { 1582 inst.prevDate = plugin.newDate(inst.drawDate); 1583 var show = this._checkMinMax((year != null ? 1584 plugin.newDate(year, month, 1) : plugin.today()), inst); 1585 inst.drawDate = plugin.newDate(show.getFullYear(), show.getMonth() + 1, 1586 (day != null ? day : Math.min(inst.drawDate.getDate(), 1587 plugin.daysInMonth(show.getFullYear(), show.getMonth() + 1)))); 1588 this._update(target); 1589 } 1590 }, 1591 1592 /* Adjust the currently shown month. 1593 @param target (element) the control to affect 1594 @param offset (number) the number of months to change by */ 1595 _changeMonthPlugin: function(target, offset) { 1596 var inst = $.data(target, this.propertyName); 1597 if (inst) { 1598 var date = plugin.add(plugin.newDate(inst.drawDate), offset, 'm'); 1599 this._showMonthPlugin(target, date.getFullYear(), date.getMonth() + 1); 1600 } 1601 }, 1602 1603 /* Adjust the currently shown day. 1604 @param target (element) the control to affect 1605 @param offset (number) the number of days to change by */ 1606 _changeDayPlugin: function(target, offset) { 1607 var inst = $.data(target, this.propertyName); 1608 if (inst) { 1609 var date = plugin.add(plugin.newDate(inst.drawDate), offset, 'd'); 1610 this._showMonthPlugin(target, date.getFullYear(), date.getMonth() + 1, date.getDate()); 1611 } 1612 }, 1613 1614 /* Restrict a date to the minimum/maximum specified. 1615 @param date (CDate) the date to check 1616 @param inst (object) the current instance settings */ 1617 _checkMinMax: function(date, inst) { 1618 var minDate = inst.get('minDate'); 1619 var maxDate = inst.get('maxDate'); 1620 date = (minDate && date.getTime() < minDate.getTime() ? plugin.newDate(minDate) : date); 1621 date = (maxDate && date.getTime() > maxDate.getTime() ? plugin.newDate(maxDate) : date); 1622 return date; 1623 }, 1624 1625 /* Retrieve the date associated with an entry in the datepicker. 1626 @param target (element) the control to examine 1627 @param elem (element) the selected datepicker element 1628 @return (CDate) the corresponding date, or null */ 1629 _retrieveDatePlugin: function(target, elem) { 1630 var inst = $.data(target, this.propertyName); 1631 return (!inst ? null : this._normaliseDate( 1632 new Date(parseInt(elem.className.replace(/^.*dp(-?\d+).*$/, '$1'), 10)))); 1633 }, 1634 1635 /* Select a date for this datepicker. 1636 @param target (element) the control to examine 1637 @param elem (element) the selected datepicker element */ 1638 _selectDatePlugin: function(target, elem) { 1639 var inst = $.data(target, this.propertyName); 1640 if (inst && !this._isDisabledPlugin(target)) { 1641 var date = this._retrieveDatePlugin(target, elem); 1642 if (inst.options.multiSelect) { 1643 var found = false; 1644 for (var i = 0; i < inst.selectedDates.length; i++) { 1645 if (date.getTime() == inst.selectedDates[i].getTime()) { 1646 inst.selectedDates.splice(i, 1); 1647 found = true; 1648 break; 1649 } 1650 } 1651 if (!found && inst.selectedDates.length < inst.options.multiSelect) { 1652 inst.selectedDates.push(date); 1653 } 1654 } 1655 else if (inst.options.rangeSelect) { 1656 if (inst.pickingRange) { 1657 inst.selectedDates[1] = date; 1658 } 1659 else { 1660 inst.selectedDates = [date, date]; 1661 } 1662 inst.pickingRange = !inst.pickingRange; 1663 } 1664 else { 1665 inst.selectedDates = [date]; 1666 } 1667 inst.prevDate = plugin.newDate(date); 1668 this._updateInput(target); 1669 if (inst.inline || inst.pickingRange || inst.selectedDates.length < 1670 (inst.options.multiSelect || (inst.options.rangeSelect ? 2 : 1))) { 1671 this._update(target); 1672 } 1673 else { 1674 this._hidePlugin(target); 1675 } 1676 } 1677 }, 1678 1679 /* Generate the datepicker content for this control. 1680 @param target (element) the control to affect 1681 @param inst (object) the current instance settings 1682 @return (jQuery) the datepicker content */ 1683 _generateContent: function(target, inst) { 1684 var monthsToShow = inst.options.monthsToShow; 1685 monthsToShow = ($.isArray(monthsToShow) ? monthsToShow : [1, monthsToShow]); 1686 inst.drawDate = this._checkMinMax( 1687 inst.drawDate || inst.get('defaultDate') || plugin.today(), inst); 1688 var drawDate = plugin._applyMonthsOffset(plugin.newDate(inst.drawDate), inst); 1689 // Generate months 1690 var monthRows = ''; 1691 for (var row = 0; row < monthsToShow[0]; row++) { 1692 var months = ''; 1693 for (var col = 0; col < monthsToShow[1]; col++) { 1694 months += this._generateMonth(target, inst, drawDate.getFullYear(), 1695 drawDate.getMonth() + 1, inst.options.renderer, (row == 0 && col == 0)); 1696 plugin.add(drawDate, 1, 'm'); 1697 } 1698 monthRows += this._prepare(inst.options.renderer.monthRow, inst).replace(/\{months\}/, months); 1699 } 1700 var picker = this._prepare(inst.options.renderer.picker, inst).replace(/\{months\}/, monthRows). 1701 replace(/\{weekHeader\}/g, this._generateDayHeaders(inst, inst.options.renderer)); 1702 // Add commands 1703 var addCommand = function(type, open, close, name, classes) { 1704 if (picker.indexOf('{' + type + ':' + name + '}') == -1) { 1705 return; 1706 } 1707 var command = inst.options.commands[name]; 1708 var date = (inst.options.commandsAsDateFormat ? command.date.apply(target, [inst]) : null); 1709 picker = picker.replace(new RegExp('\\{' + type + ':' + name + '\\}', 'g'), 1710 '<' + open + 1711 (command.status ? ' title="' + inst.options[command.status] + '"' : '') + 1712 ' class="' + inst.options.renderer.commandClass + ' ' + 1713 inst.options.renderer.commandClass + '-' + name + ' ' + classes + 1714 (command.enabled(inst) ? '' : ' ' + inst.options.renderer.disabledClass) + '">' + 1715 (date ? plugin.formatDate(inst.options[command.text], date, inst.getConfig()) : 1716 inst.options[command.text]) + '</' + close + '>'); 1717 }; 1718 for (var name in inst.options.commands) { 1719 addCommand('button', 'button type="button"', 'button', name, 1720 inst.options.renderer.commandButtonClass); 1721 addCommand('link', 'a href="javascript:void(0)"', 'a', name, 1722 inst.options.renderer.commandLinkClass); 1723 } 1724 picker = $(picker); 1725 if (monthsToShow[1] > 1) { 1726 var count = 0; 1727 $(inst.options.renderer.monthSelector, picker).each(function() { 1728 var nth = ++count % monthsToShow[1]; 1729 $(this).addClass(nth == 1 ? 'first' : (nth == 0 ? 'last' : '')); 1730 }); 1731 } 1732 // Add datepicker behaviour 1733 var self = this; 1734 picker.find(inst.options.renderer.daySelector + ' a').hover( 1735 function() { $(this).addClass(inst.options.renderer.highlightedClass); }, 1736 function() { 1737 (inst.inline ? $(this).parents('.' + self.markerClassName) : inst.div). 1738 find(inst.options.renderer.daySelector + ' a'). 1739 removeClass(inst.options.renderer.highlightedClass); 1740 }). 1741 click(function() { 1742 self._selectDatePlugin(target, this); 1743 }).end(). 1744 find('select.' + this._monthYearClass + ':not(.' + this._anyYearClass + ')'). 1745 change(function() { 1746 var monthYear = $(this).val().split('/'); 1747 self._showMonthPlugin(target, parseInt(monthYear[1], 10), parseInt(monthYear[0], 10)); 1748 }).end(). 1749 find('select.' + this._anyYearClass).click(function() { 1750 $(this).css('visibility', 'hidden'). 1751 next('input').css({left: this.offsetLeft, top: this.offsetTop, 1752 width: this.offsetWidth, height: this.offsetHeight}).show().focus(); 1753 }).end(). 1754 find('input.' + self._monthYearClass).change(function() { 1755 try { 1756 var year = parseInt($(this).val(), 10); 1757 year = (isNaN(year) ? inst.drawDate.getFullYear() : year); 1758 self._showMonthPlugin(target, year, inst.drawDate.getMonth() + 1, inst.drawDate.getDate()); 1759 } 1760 catch (e) { 1761 alert(e); 1762 } 1763 }).keydown(function(event) { 1764 if (event.keyCode == 13) { // Enter 1765 $(event.target).change(); 1766 } 1767 else if (event.keyCode == 27) { // Escape 1768 $(event.target).hide().prev('select').css('visibility', 'visible'); 1769 inst.target.focus(); 1770 } 1771 }); 1772 // Add command behaviour 1773 picker.find('.' + inst.options.renderer.commandClass).click(function() { 1774 if (!$(this).hasClass(inst.options.renderer.disabledClass)) { 1775 var action = this.className.replace( 1776 new RegExp('^.*' + inst.options.renderer.commandClass + '-([^ ]+).*$'), '$1'); 1777 plugin._performActionPlugin(target, action); 1778 } 1779 }); 1780 // Add classes 1781 if (inst.options.isRTL) { 1782 picker.addClass(inst.options.renderer.rtlClass); 1783 } 1784 if (monthsToShow[0] * monthsToShow[1] > 1) { 1785 picker.addClass(inst.options.renderer.multiClass); 1786 } 1787 if (inst.options.pickerClass) { 1788 picker.addClass(inst.options.pickerClass); 1789 } 1790 // Resize 1791 $('body').append(picker); 1792 var width = 0; 1793 picker.find(inst.options.renderer.monthSelector).each(function() { 1794 width += $(this).outerWidth(); 1795 }); 1796 picker.width(width / monthsToShow[0]); 1797 // Pre-show customisation 1798 if ($.isFunction(inst.options.onShow)) { 1799 inst.options.onShow.apply(target, [picker, inst]); 1800 } 1801 return picker; 1802 }, 1803 1804 /* Generate the content for a single month. 1805 @param target (element) the control to affect 1806 @param inst (object) the current instance settings 1807 @param year (number) the year to generate 1808 @param month (number) the month to generate 1809 @param renderer (object) the rendering templates 1810 @param first (boolean) true if first of multiple months 1811 @return (string) the month content */ 1812 _generateMonth: function(target, inst, year, month, renderer, first) { 1813 var daysInMonth = plugin.daysInMonth(year, month); 1814 var monthsToShow = inst.options.monthsToShow; 1815 monthsToShow = ($.isArray(monthsToShow) ? monthsToShow : [1, monthsToShow]); 1816 var fixedWeeks = inst.options.fixedWeeks || (monthsToShow[0] * monthsToShow[1] > 1); 1817 var firstDay = inst.options.firstDay; 1818 var leadDays = (plugin.newDate(year, month, 1).getDay() - firstDay + 7) % 7; 1819 var numWeeks = (fixedWeeks ? 6 : Math.ceil((leadDays + daysInMonth) / 7)); 1820 var selectOtherMonths = inst.options.selectOtherMonths && inst.options.showOtherMonths; 1821 var minDate = (inst.pickingRange ? inst.selectedDates[0] : inst.get('minDate')); 1822 var maxDate = inst.get('maxDate'); 1823 var showWeeks = renderer.week.indexOf('{weekOfYear}') > -1; 1824 var today = plugin.today(); 1825 var drawDate = plugin.newDate(year, month, 1); 1826 plugin.add(drawDate, -leadDays - (fixedWeeks && (drawDate.getDay() == firstDay) ? 7 : 0), 'd'); 1827 var ts = drawDate.getTime(); 1828 // Generate weeks 1829 var weeks = ''; 1830 for (var week = 0; week < numWeeks; week++) { 1831 var weekOfYear = (!showWeeks ? '' : '<span class="dp' + ts + '">' + 1832 ($.isFunction(inst.options.calculateWeek) ? inst.options.calculateWeek(drawDate) : 0) + '</span>'); 1833 var days = ''; 1834 for (var day = 0; day < 7; day++) { 1835 var selected = false; 1836 if (inst.options.rangeSelect && inst.selectedDates.length > 0) { 1837 selected = (drawDate.getTime() >= inst.selectedDates[0] && 1838 drawDate.getTime() <= inst.selectedDates[1]); 1839 } 1840 else { 1841 for (var i = 0; i < inst.selectedDates.length; i++) { 1842 if (inst.selectedDates[i].getTime() == drawDate.getTime()) { 1843 selected = true; 1844 break; 1845 } 1846 } 1847 } 1848 var dateInfo = (!$.isFunction(inst.options.onDate) ? {} : 1849 inst.options.onDate.apply(target, [drawDate, drawDate.getMonth() + 1 == month])); 1850 var selectable = (selectOtherMonths || drawDate.getMonth() + 1 == month) && 1851 this._isSelectable(target, drawDate, dateInfo.selectable, minDate, maxDate); 1852 days += this._prepare(renderer.day, inst).replace(/\{day\}/g, 1853 (selectable ? '<a href="javascript:void(0)"' : '<span') + 1854 ' class="dp' + ts + ' ' + (dateInfo.dateClass || '') + 1855 (selected && (selectOtherMonths || drawDate.getMonth() + 1 == month) ? 1856 ' ' + renderer.selectedClass : '') + 1857 (selectable ? ' ' + renderer.defaultClass : '') + 1858 ((drawDate.getDay() || 7) < 6 ? '' : ' ' + renderer.weekendClass) + 1859 (drawDate.getMonth() + 1 == month ? '' : ' ' + renderer.otherMonthClass) + 1860 (drawDate.getTime() == today.getTime() && (drawDate.getMonth() + 1) == month ? 1861 ' ' + renderer.todayClass : '') + 1862 (drawDate.getTime() == inst.drawDate.getTime() && (drawDate.getMonth() + 1) == month ? 1863 ' ' + renderer.highlightedClass : '') + '"' + 1864 (dateInfo.title || (inst.options.dayStatus && selectable) ? ' title="' + 1865 (dateInfo.title || plugin.formatDate( 1866 inst.options.dayStatus, drawDate, inst.getConfig())) + '"' : '') + '>' + 1867 (inst.options.showOtherMonths || (drawDate.getMonth() + 1) == month ? 1868 dateInfo.content || drawDate.getDate() : ' ') + 1869 (selectable ? '</a>' : '</span>')); 1870 plugin.add(drawDate, 1, 'd'); 1871 ts = drawDate.getTime(); 1872 } 1873 weeks += this._prepare(renderer.week, inst).replace(/\{days\}/g, days). 1874 replace(/\{weekOfYear\}/g, weekOfYear); 1875 } 1876 var monthHeader = this._prepare(renderer.month, inst).match(/\{monthHeader(:[^\}]+)?\}/); 1877 monthHeader = (monthHeader[0].length <= 13 ? 'MM yyyy' : 1878 monthHeader[0].substring(13, monthHeader[0].length - 1)); 1879 monthHeader = (first ? this._generateMonthSelection( 1880 inst, year, month, minDate, maxDate, monthHeader, renderer) : 1881 plugin.formatDate(monthHeader, plugin.newDate(year, month, 1), inst.getConfig())); 1882 var weekHeader = this._prepare(renderer.weekHeader, inst). 1883 replace(/\{days\}/g, this._generateDayHeaders(inst, renderer)); 1884 return this._prepare(renderer.month, inst).replace(/\{monthHeader(:[^\}]+)?\}/g, monthHeader). 1885 replace(/\{weekHeader\}/g, weekHeader).replace(/\{weeks\}/g, weeks); 1886 }, 1887 1888 /* Generate the HTML for the day headers. 1889 @param inst (object) the current instance settings 1890 @param renderer (object) the rendering templates 1891 @return (string) a week's worth of day headers */ 1892 _generateDayHeaders: function(inst, renderer) { 1893 var header = ''; 1894 for (var day = 0; day < 7; day++) { 1895 var dow = (day + inst.options.firstDay) % 7; 1896 header += this._prepare(renderer.dayHeader, inst).replace(/\{day\}/g, 1897 '<span class="' + this._curDoWClass + dow + '" title="' + 1898 inst.options.dayNames[dow] + '">' + inst.options.dayNamesMin[dow] + '</span>'); 1899 } 1900 return header; 1901 }, 1902 1903 /* Generate selection controls for month. 1904 @param inst (object) the current instance settings 1905 @param year (number) the year to generate 1906 @param month (number) the month to generate 1907 @param minDate (CDate) the minimum date allowed 1908 @param maxDate (CDate) the maximum date allowed 1909 @param monthHeader (string) the month/year format 1910 @return (string) the month selection content */ 1911 _generateMonthSelection: function(inst, year, month, minDate, maxDate, monthHeader) { 1912 if (!inst.options.changeMonth) { 1913 return plugin.formatDate( 1914 monthHeader, plugin.newDate(year, month, 1), inst.getConfig()); 1915 } 1916 // Months 1917 var monthNames = inst.options['monthNames' + (monthHeader.match(/mm/i) ? '' : 'Short')]; 1918 var html = monthHeader.replace(/m+/i, '\\x2E').replace(/y+/i, '\\x2F'); 1919 var selector = '<select class="' + this._monthYearClass + 1920 '" title="' + inst.options.monthStatus + '">'; 1921 for (var m = 1; m <= 12; m++) { 1922 if ((!minDate || plugin.newDate(year, m, plugin.daysInMonth(year, m)). 1923 getTime() >= minDate.getTime()) && 1924 (!maxDate || plugin.newDate(year, m, 1).getTime() <= maxDate.getTime())) { 1925 selector += '<option value="' + m + '/' + year + '"' + 1926 (month == m ? ' selected="selected"' : '') + '>' + 1927 monthNames[m - 1] + '</option>'; 1928 } 1929 } 1930 selector += '</select>'; 1931 html = html.replace(/\\x2E/, selector); 1932 // Years 1933 var yearRange = inst.options.yearRange; 1934 if (yearRange == 'any') { 1935 selector = '<select class="' + this._monthYearClass + ' ' + this._anyYearClass + 1936 '" title="' + inst.options.yearStatus + '">' + 1937 '<option>' + year + '</option></select>' + 1938 '<input class="' + this._monthYearClass + ' ' + this._curMonthClass + 1939 month + '" value="' + year + '">'; 1940 } 1941 else { 1942 yearRange = yearRange.split(':'); 1943 var todayYear = plugin.today().getFullYear(); 1944 var start = (yearRange[0].match('c[+-].*') ? year + parseInt(yearRange[0].substring(1), 10) : 1945 ((yearRange[0].match('[+-].*') ? todayYear : 0) + parseInt(yearRange[0], 10))); 1946 var end = (yearRange[1].match('c[+-].*') ? year + parseInt(yearRange[1].substring(1), 10) : 1947 ((yearRange[1].match('[+-].*') ? todayYear : 0) + parseInt(yearRange[1], 10))); 1948 selector = '<select class="' + this._monthYearClass + 1949 '" title="' + inst.options.yearStatus + '">'; 1950 start = plugin.add(plugin.newDate(start + 1, 1, 1), -1, 'd'); 1951 end = plugin.newDate(end, 1, 1); 1952 var addYear = function(y) { 1953 if (y != 0) { 1954 selector += '<option value="' + month + '/' + y + '"' + 1955 (year == y ? ' selected="selected"' : '') + '>' + y + '</option>'; 1956 } 1957 }; 1958 if (start.getTime() < end.getTime()) { 1959 start = (minDate && minDate.getTime() > start.getTime() ? minDate : start).getFullYear(); 1960 end = (maxDate && maxDate.getTime() < end.getTime() ? maxDate : end).getFullYear(); 1961 for (var y = start; y <= end; y++) { 1962 addYear(y); 1963 } 1964 } 1965 else { 1966 start = (maxDate && maxDate.getTime() < start.getTime() ? maxDate : start).getFullYear(); 1967 end = (minDate && minDate.getTime() > end.getTime() ? minDate : end).getFullYear(); 1968 for (var y = start; y >= end; y--) { 1969 addYear(y); 1970 } 1971 } 1972 selector += '</select>'; 1973 } 1974 html = html.replace(/\\x2F/, selector); 1975 return html; 1976 }, 1977 1978 /* Prepare a render template for use. 1979 Exclude popup/inline sections that are not applicable. 1980 Localise text of the form: {l10n:name}. 1981 @param text (string) the text to localise 1982 @param inst (object) the current instance settings 1983 @return (string) the localised text */ 1984 _prepare: function(text, inst) { 1985 var replaceSection = function(type, retain) { 1986 while (true) { 1987 var start = text.indexOf('{' + type + ':start}'); 1988 if (start == -1) { 1989 return; 1990 } 1991 var end = text.substring(start).indexOf('{' + type + ':end}'); 1992 if (end > -1) { 1993 text = text.substring(0, start) + 1994 (retain ? text.substr(start + type.length + 8, end - type.length - 8) : '') + 1995 text.substring(start + end + type.length + 6); 1996 } 1997 } 1998 }; 1999 replaceSection('inline', inst.inline); 2000 replaceSection('popup', !inst.inline); 2001 var pattern = /\{l10n:([^\}]+)\}/; 2002 var matches = null; 2003 while (matches = pattern.exec(text)) { 2004 text = text.replace(matches[0], inst.options[matches[1]]); 2005 } 2006 return text; 2007 } 2008 }); 2009 2010 // The list of commands that return values and don't permit chaining 2011 var getters = ['getDate', 'isDisabled', 'isSelectable', 'retrieveDate']; 2012 2013 /* Determine whether a command is a getter and doesn't permit chaining. 2014 @param command (string, optional) the command to run 2015 @param otherArgs ([], optional) any other arguments for the command 2016 @return true if the command is a getter, false if not */ 2017 function isNotChained(command, otherArgs) { 2018 if (command == 'option' && (otherArgs.length == 0 || 2019 (otherArgs.length == 1 && typeof otherArgs[0] == 'string'))) { 2020 return true; 2021 } 2022 return $.inArray(command, getters) > -1; 2023 } 2024 2025 /* Attach the datepicker functionality to a jQuery selection. 2026 @param options (object) the new settings to use for these instances (optional) or 2027 (string) the command to run (optional) 2028 @return (jQuery) for chaining further calls or 2029 (any) getter value */ 2030 $.fn.datepick = function(options) { 2031 var otherArgs = Array.prototype.slice.call(arguments, 1); 2032 if (isNotChained(options, otherArgs)) { 2033 return plugin['_' + options + 'Plugin'].apply(plugin, [this[0]].concat(otherArgs)); 2034 } 2035 return this.each(function() { 2036 if (typeof options == 'string') { 2037 if (!plugin['_' + options + 'Plugin']) { 2038 throw 'Unknown command: ' + options; 2039 } 2040 plugin['_' + options + 'Plugin'].apply(plugin, [this].concat(otherArgs)); 2041 } 2042 else { 2043 plugin._attachPlugin(this, options || {}); 2044 } 2045 }); 2046 }; 2047 2048 /* Initialise the datepicker functionality. */ 2049 var plugin = $.datepick = new Datepicker(); // Singleton instance 2050 2051 $(function() { 2052 $(document).mousedown(plugin._checkExternalClick). 2053 resize(function() { plugin._hidePlugin(plugin.curInst); }); 2054 }); 2055 2056 })(jQuery);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:08:37 2014 | Cross-referenced by PHPXref 0.7.1 |