[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 /* eslint-disable max-len, camelcase */ 2 /*! 3 * jQuery UI Datepicker 1.13.3 4 * https://jqueryui.com 5 * 6 * Copyright OpenJS Foundation and other contributors 7 * Released under the MIT license. 8 * https://jquery.org/license 9 */ 10 11 //>>label: Datepicker 12 //>>group: Widgets 13 //>>description: Displays a calendar from an input or inline for selecting dates. 14 //>>docs: https://api.jqueryui.com/datepicker/ 15 //>>demos: https://jqueryui.com/datepicker/ 16 //>>css.structure: ../../themes/base/core.css 17 //>>css.structure: ../../themes/base/datepicker.css 18 //>>css.theme: ../../themes/base/theme.css 19 20 ( function( factory ) { 21 "use strict"; 22 23 if ( typeof define === "function" && define.amd ) { 24 25 // AMD. Register as an anonymous module. 26 define( [ 27 "jquery", 28 "../version", 29 "../keycode" 30 ], factory ); 31 } else { 32 33 // Browser globals 34 factory( jQuery ); 35 } 36 } )( function( $ ) { 37 "use strict"; 38 39 $.extend( $.ui, { datepicker: { version: "1.13.3" } } ); 40 41 var datepicker_instActive; 42 43 function datepicker_getZindex( elem ) { 44 var position, value; 45 while ( elem.length && elem[ 0 ] !== document ) { 46 47 // Ignore z-index if position is set to a value where z-index is ignored by the browser 48 // This makes behavior of this function consistent across browsers 49 // WebKit always returns auto if the element is positioned 50 position = elem.css( "position" ); 51 if ( position === "absolute" || position === "relative" || position === "fixed" ) { 52 53 // IE returns 0 when zIndex is not specified 54 // other browsers return a string 55 // we ignore the case of nested elements with an explicit value of 0 56 // <div style="z-index: -10;"><div style="z-index: 0;"></div></div> 57 value = parseInt( elem.css( "zIndex" ), 10 ); 58 if ( !isNaN( value ) && value !== 0 ) { 59 return value; 60 } 61 } 62 elem = elem.parent(); 63 } 64 65 return 0; 66 } 67 68 /* Date picker manager. 69 Use the singleton instance of this class, $.datepicker, to interact with the date picker. 70 Settings for (groups of) date pickers are maintained in an instance object, 71 allowing multiple different settings on the same page. */ 72 73 function Datepicker() { 74 this._curInst = null; // The current instance in use 75 this._keyEvent = false; // If the last event was a key event 76 this._disabledInputs = []; // List of date picker inputs that have been disabled 77 this._datepickerShowing = false; // True if the popup picker is showing , false if not 78 this._inDialog = false; // True if showing within a "dialog", false if not 79 this._mainDivId = "ui-datepicker-div"; // The ID of the main datepicker division 80 this._inlineClass = "ui-datepicker-inline"; // The name of the inline marker class 81 this._appendClass = "ui-datepicker-append"; // The name of the append marker class 82 this._triggerClass = "ui-datepicker-trigger"; // The name of the trigger marker class 83 this._dialogClass = "ui-datepicker-dialog"; // The name of the dialog marker class 84 this._disableClass = "ui-datepicker-disabled"; // The name of the disabled covering marker class 85 this._unselectableClass = "ui-datepicker-unselectable"; // The name of the unselectable cell marker class 86 this._currentClass = "ui-datepicker-current-day"; // The name of the current day marker class 87 this._dayOverClass = "ui-datepicker-days-cell-over"; // The name of the day hover marker class 88 this.regional = []; // Available regional settings, indexed by language code 89 this.regional[ "" ] = { // Default regional settings 90 closeText: "Done", // Display text for close link 91 prevText: "Prev", // Display text for previous month link 92 nextText: "Next", // Display text for next month link 93 currentText: "Today", // Display text for current month link 94 monthNames: [ "January", "February", "March", "April", "May", "June", 95 "July", "August", "September", "October", "November", "December" ], // Names of months for drop-down and formatting 96 monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ], // For formatting 97 dayNames: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], // For formatting 98 dayNamesShort: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], // For formatting 99 dayNamesMin: [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ], // Column headings for days starting at Sunday 100 weekHeader: "Wk", // Column header for week of the year 101 dateFormat: "mm/dd/yy", // See format options on parseDate 102 firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ... 103 isRTL: false, // True if right-to-left language, false if left-to-right 104 showMonthAfterYear: false, // True if the year select precedes month, false for month then year 105 yearSuffix: "", // Additional text to append to the year in the month headers, 106 selectMonthLabel: "Select month", // Invisible label for month selector 107 selectYearLabel: "Select year" // Invisible label for year selector 108 }; 109 this._defaults = { // Global defaults for all the date picker instances 110 showOn: "focus", // "focus" for popup on focus, 111 // "button" for trigger button, or "both" for either 112 showAnim: "fadeIn", // Name of jQuery animation for popup 113 showOptions: {}, // Options for enhanced animations 114 defaultDate: null, // Used when field is blank: actual date, 115 // +/-number for offset from today, null for today 116 appendText: "", // Display text following the input box, e.g. showing the format 117 buttonText: "...", // Text for trigger button 118 buttonImage: "", // URL for trigger button image 119 buttonImageOnly: false, // True if the image appears alone, false if it appears on a button 120 hideIfNoPrevNext: false, // True to hide next/previous month links 121 // if not applicable, false to just disable them 122 navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links 123 gotoCurrent: false, // True if today link goes back to current selection instead 124 changeMonth: false, // True if month can be selected directly, false if only prev/next 125 changeYear: false, // True if year can be selected directly, false if only prev/next 126 yearRange: "c-10:c+10", // Range of years to display in drop-down, 127 // either relative to today's year (-nn:+nn), relative to currently displayed year 128 // (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n) 129 showOtherMonths: false, // True to show dates in other months, false to leave blank 130 selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable 131 showWeek: false, // True to show week of the year, false to not show it 132 calculateWeek: this.iso8601Week, // How to calculate the week of the year, 133 // takes a Date and returns the number of the week for it 134 shortYearCutoff: "+10", // Short year values < this are in the current century, 135 // > this are in the previous century, 136 // string value starting with "+" for current year + value 137 minDate: null, // The earliest selectable date, or null for no limit 138 maxDate: null, // The latest selectable date, or null for no limit 139 duration: "fast", // Duration of display/closure 140 beforeShowDay: null, // Function that takes a date and returns an array with 141 // [0] = true if selectable, false if not, [1] = custom CSS class name(s) or "", 142 // [2] = cell title (optional), e.g. $.datepicker.noWeekends 143 beforeShow: null, // Function that takes an input field and 144 // returns a set of custom settings for the date picker 145 onSelect: null, // Define a callback function when a date is selected 146 onChangeMonthYear: null, // Define a callback function when the month or year is changed 147 onClose: null, // Define a callback function when the datepicker is closed 148 onUpdateDatepicker: null, // Define a callback function when the datepicker is updated 149 numberOfMonths: 1, // Number of months to show at a time 150 showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0) 151 stepMonths: 1, // Number of months to step back/forward 152 stepBigMonths: 12, // Number of months to step back/forward for the big links 153 altField: "", // Selector for an alternate field to store selected dates into 154 altFormat: "", // The date format to use for the alternate field 155 constrainInput: true, // The input is constrained by the current date format 156 showButtonPanel: false, // True to show button panel, false to not show it 157 autoSize: false, // True to size the input for the date format, false to leave as is 158 disabled: false // The initial disabled state 159 }; 160 $.extend( this._defaults, this.regional[ "" ] ); 161 this.regional.en = $.extend( true, {}, this.regional[ "" ] ); 162 this.regional[ "en-US" ] = $.extend( true, {}, this.regional.en ); 163 this.dpDiv = datepicker_bindHover( $( "<div id='" + this._mainDivId + "' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>" ) ); 164 } 165 166 $.extend( Datepicker.prototype, { 167 168 /* Class name added to elements to indicate already configured with a date picker. */ 169 markerClassName: "hasDatepicker", 170 171 //Keep track of the maximum number of rows displayed (see #7043) 172 maxRows: 4, 173 174 // TODO rename to "widget" when switching to widget factory 175 _widgetDatepicker: function() { 176 return this.dpDiv; 177 }, 178 179 /* Override the default settings for all instances of the date picker. 180 * @param settings object - the new settings to use as defaults (anonymous object) 181 * @return the manager object 182 */ 183 setDefaults: function( settings ) { 184 datepicker_extendRemove( this._defaults, settings || {} ); 185 return this; 186 }, 187 188 /* Attach the date picker to a jQuery selection. 189 * @param target element - the target input field or division or span 190 * @param settings object - the new settings to use for this date picker instance (anonymous) 191 */ 192 _attachDatepicker: function( target, settings ) { 193 var nodeName, inline, inst; 194 nodeName = target.nodeName.toLowerCase(); 195 inline = ( nodeName === "div" || nodeName === "span" ); 196 if ( !target.id ) { 197 this.uuid += 1; 198 target.id = "dp" + this.uuid; 199 } 200 inst = this._newInst( $( target ), inline ); 201 inst.settings = $.extend( {}, settings || {} ); 202 if ( nodeName === "input" ) { 203 this._connectDatepicker( target, inst ); 204 } else if ( inline ) { 205 this._inlineDatepicker( target, inst ); 206 } 207 }, 208 209 /* Create a new instance object. */ 210 _newInst: function( target, inline ) { 211 var id = target[ 0 ].id.replace( /([^A-Za-z0-9_\-])/g, "\\\\$1" ); // escape jQuery meta chars 212 return { id: id, input: target, // associated target 213 selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection 214 drawMonth: 0, drawYear: 0, // month being drawn 215 inline: inline, // is datepicker inline or not 216 dpDiv: ( !inline ? this.dpDiv : // presentation div 217 datepicker_bindHover( $( "<div class='" + this._inlineClass + " ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>" ) ) ) }; 218 }, 219 220 /* Attach the date picker to an input field. */ 221 _connectDatepicker: function( target, inst ) { 222 var input = $( target ); 223 inst.append = $( [] ); 224 inst.trigger = $( [] ); 225 if ( input.hasClass( this.markerClassName ) ) { 226 return; 227 } 228 this._attachments( input, inst ); 229 input.addClass( this.markerClassName ).on( "keydown", this._doKeyDown ). 230 on( "keypress", this._doKeyPress ).on( "keyup", this._doKeyUp ); 231 this._autoSize( inst ); 232 $.data( target, "datepicker", inst ); 233 234 //If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665) 235 if ( inst.settings.disabled ) { 236 this._disableDatepicker( target ); 237 } 238 }, 239 240 /* Make attachments based on settings. */ 241 _attachments: function( input, inst ) { 242 var showOn, buttonText, buttonImage, 243 appendText = this._get( inst, "appendText" ), 244 isRTL = this._get( inst, "isRTL" ); 245 246 if ( inst.append ) { 247 inst.append.remove(); 248 } 249 if ( appendText ) { 250 inst.append = $( "<span>" ) 251 .addClass( this._appendClass ) 252 .text( appendText ); 253 input[ isRTL ? "before" : "after" ]( inst.append ); 254 } 255 256 input.off( "focus", this._showDatepicker ); 257 258 if ( inst.trigger ) { 259 inst.trigger.remove(); 260 } 261 262 showOn = this._get( inst, "showOn" ); 263 if ( showOn === "focus" || showOn === "both" ) { // pop-up date picker when in the marked field 264 input.on( "focus", this._showDatepicker ); 265 } 266 if ( showOn === "button" || showOn === "both" ) { // pop-up date picker when button clicked 267 buttonText = this._get( inst, "buttonText" ); 268 buttonImage = this._get( inst, "buttonImage" ); 269 270 if ( this._get( inst, "buttonImageOnly" ) ) { 271 inst.trigger = $( "<img>" ) 272 .addClass( this._triggerClass ) 273 .attr( { 274 src: buttonImage, 275 alt: buttonText, 276 title: buttonText 277 } ); 278 } else { 279 inst.trigger = $( "<button type='button'>" ) 280 .addClass( this._triggerClass ); 281 if ( buttonImage ) { 282 inst.trigger.html( 283 $( "<img>" ) 284 .attr( { 285 src: buttonImage, 286 alt: buttonText, 287 title: buttonText 288 } ) 289 ); 290 } else { 291 inst.trigger.text( buttonText ); 292 } 293 } 294 295 input[ isRTL ? "before" : "after" ]( inst.trigger ); 296 inst.trigger.on( "click", function() { 297 if ( $.datepicker._datepickerShowing && $.datepicker._lastInput === input[ 0 ] ) { 298 $.datepicker._hideDatepicker(); 299 } else if ( $.datepicker._datepickerShowing && $.datepicker._lastInput !== input[ 0 ] ) { 300 $.datepicker._hideDatepicker(); 301 $.datepicker._showDatepicker( input[ 0 ] ); 302 } else { 303 $.datepicker._showDatepicker( input[ 0 ] ); 304 } 305 return false; 306 } ); 307 } 308 }, 309 310 /* Apply the maximum length for the date format. */ 311 _autoSize: function( inst ) { 312 if ( this._get( inst, "autoSize" ) && !inst.inline ) { 313 var findMax, max, maxI, i, 314 date = new Date( 2009, 12 - 1, 20 ), // Ensure double digits 315 dateFormat = this._get( inst, "dateFormat" ); 316 317 if ( dateFormat.match( /[DM]/ ) ) { 318 findMax = function( names ) { 319 max = 0; 320 maxI = 0; 321 for ( i = 0; i < names.length; i++ ) { 322 if ( names[ i ].length > max ) { 323 max = names[ i ].length; 324 maxI = i; 325 } 326 } 327 return maxI; 328 }; 329 date.setMonth( findMax( this._get( inst, ( dateFormat.match( /MM/ ) ? 330 "monthNames" : "monthNamesShort" ) ) ) ); 331 date.setDate( findMax( this._get( inst, ( dateFormat.match( /DD/ ) ? 332 "dayNames" : "dayNamesShort" ) ) ) + 20 - date.getDay() ); 333 } 334 inst.input.attr( "size", this._formatDate( inst, date ).length ); 335 } 336 }, 337 338 /* Attach an inline date picker to a div. */ 339 _inlineDatepicker: function( target, inst ) { 340 var divSpan = $( target ); 341 if ( divSpan.hasClass( this.markerClassName ) ) { 342 return; 343 } 344 divSpan.addClass( this.markerClassName ).append( inst.dpDiv ); 345 $.data( target, "datepicker", inst ); 346 this._setDate( inst, this._getDefaultDate( inst ), true ); 347 this._updateDatepicker( inst ); 348 this._updateAlternate( inst ); 349 350 //If disabled option is true, disable the datepicker before showing it (see ticket #5665) 351 if ( inst.settings.disabled ) { 352 this._disableDatepicker( target ); 353 } 354 355 // Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements 356 // https://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height 357 inst.dpDiv.css( "display", "block" ); 358 }, 359 360 /* Pop-up the date picker in a "dialog" box. 361 * @param input element - ignored 362 * @param date string or Date - the initial date to display 363 * @param onSelect function - the function to call when a date is selected 364 * @param settings object - update the dialog date picker instance's settings (anonymous object) 365 * @param pos int[2] - coordinates for the dialog's position within the screen or 366 * event - with x/y coordinates or 367 * leave empty for default (screen centre) 368 * @return the manager object 369 */ 370 _dialogDatepicker: function( input, date, onSelect, settings, pos ) { 371 var id, browserWidth, browserHeight, scrollX, scrollY, 372 inst = this._dialogInst; // internal instance 373 374 if ( !inst ) { 375 this.uuid += 1; 376 id = "dp" + this.uuid; 377 this._dialogInput = $( "<input type='text' id='" + id + 378 "' style='position: absolute; top: -100px; width: 0px;'/>" ); 379 this._dialogInput.on( "keydown", this._doKeyDown ); 380 $( "body" ).append( this._dialogInput ); 381 inst = this._dialogInst = this._newInst( this._dialogInput, false ); 382 inst.settings = {}; 383 $.data( this._dialogInput[ 0 ], "datepicker", inst ); 384 } 385 datepicker_extendRemove( inst.settings, settings || {} ); 386 date = ( date && date.constructor === Date ? this._formatDate( inst, date ) : date ); 387 this._dialogInput.val( date ); 388 389 this._pos = ( pos ? ( pos.length ? pos : [ pos.pageX, pos.pageY ] ) : null ); 390 if ( !this._pos ) { 391 browserWidth = document.documentElement.clientWidth; 392 browserHeight = document.documentElement.clientHeight; 393 scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; 394 scrollY = document.documentElement.scrollTop || document.body.scrollTop; 395 this._pos = // should use actual width/height below 396 [ ( browserWidth / 2 ) - 100 + scrollX, ( browserHeight / 2 ) - 150 + scrollY ]; 397 } 398 399 // Move input on screen for focus, but hidden behind dialog 400 this._dialogInput.css( "left", ( this._pos[ 0 ] + 20 ) + "px" ).css( "top", this._pos[ 1 ] + "px" ); 401 inst.settings.onSelect = onSelect; 402 this._inDialog = true; 403 this.dpDiv.addClass( this._dialogClass ); 404 this._showDatepicker( this._dialogInput[ 0 ] ); 405 if ( $.blockUI ) { 406 $.blockUI( this.dpDiv ); 407 } 408 $.data( this._dialogInput[ 0 ], "datepicker", inst ); 409 return this; 410 }, 411 412 /* Detach a datepicker from its control. 413 * @param target element - the target input field or division or span 414 */ 415 _destroyDatepicker: function( target ) { 416 var nodeName, 417 $target = $( target ), 418 inst = $.data( target, "datepicker" ); 419 420 if ( !$target.hasClass( this.markerClassName ) ) { 421 return; 422 } 423 424 nodeName = target.nodeName.toLowerCase(); 425 $.removeData( target, "datepicker" ); 426 if ( nodeName === "input" ) { 427 inst.append.remove(); 428 inst.trigger.remove(); 429 $target.removeClass( this.markerClassName ). 430 off( "focus", this._showDatepicker ). 431 off( "keydown", this._doKeyDown ). 432 off( "keypress", this._doKeyPress ). 433 off( "keyup", this._doKeyUp ); 434 } else if ( nodeName === "div" || nodeName === "span" ) { 435 $target.removeClass( this.markerClassName ).empty(); 436 } 437 438 if ( datepicker_instActive === inst ) { 439 datepicker_instActive = null; 440 this._curInst = null; 441 } 442 }, 443 444 /* Enable the date picker to a jQuery selection. 445 * @param target element - the target input field or division or span 446 */ 447 _enableDatepicker: function( target ) { 448 var nodeName, inline, 449 $target = $( target ), 450 inst = $.data( target, "datepicker" ); 451 452 if ( !$target.hasClass( this.markerClassName ) ) { 453 return; 454 } 455 456 nodeName = target.nodeName.toLowerCase(); 457 if ( nodeName === "input" ) { 458 target.disabled = false; 459 inst.trigger.filter( "button" ). 460 each( function() { 461 this.disabled = false; 462 } ).end(). 463 filter( "img" ).css( { opacity: "1.0", cursor: "" } ); 464 } else if ( nodeName === "div" || nodeName === "span" ) { 465 inline = $target.children( "." + this._inlineClass ); 466 inline.children().removeClass( "ui-state-disabled" ); 467 inline.find( "select.ui-datepicker-month, select.ui-datepicker-year" ). 468 prop( "disabled", false ); 469 } 470 this._disabledInputs = $.map( this._disabledInputs, 471 472 // Delete entry 473 function( value ) { 474 return ( value === target ? null : value ); 475 } ); 476 }, 477 478 /* Disable the date picker to a jQuery selection. 479 * @param target element - the target input field or division or span 480 */ 481 _disableDatepicker: function( target ) { 482 var nodeName, inline, 483 $target = $( target ), 484 inst = $.data( target, "datepicker" ); 485 486 if ( !$target.hasClass( this.markerClassName ) ) { 487 return; 488 } 489 490 nodeName = target.nodeName.toLowerCase(); 491 if ( nodeName === "input" ) { 492 target.disabled = true; 493 inst.trigger.filter( "button" ). 494 each( function() { 495 this.disabled = true; 496 } ).end(). 497 filter( "img" ).css( { opacity: "0.5", cursor: "default" } ); 498 } else if ( nodeName === "div" || nodeName === "span" ) { 499 inline = $target.children( "." + this._inlineClass ); 500 inline.children().addClass( "ui-state-disabled" ); 501 inline.find( "select.ui-datepicker-month, select.ui-datepicker-year" ). 502 prop( "disabled", true ); 503 } 504 this._disabledInputs = $.map( this._disabledInputs, 505 506 // Delete entry 507 function( value ) { 508 return ( value === target ? null : value ); 509 } ); 510 this._disabledInputs[ this._disabledInputs.length ] = target; 511 }, 512 513 /* Is the first field in a jQuery collection disabled as a datepicker? 514 * @param target element - the target input field or division or span 515 * @return boolean - true if disabled, false if enabled 516 */ 517 _isDisabledDatepicker: function( target ) { 518 if ( !target ) { 519 return false; 520 } 521 for ( var i = 0; i < this._disabledInputs.length; i++ ) { 522 if ( this._disabledInputs[ i ] === target ) { 523 return true; 524 } 525 } 526 return false; 527 }, 528 529 /* Retrieve the instance data for the target control. 530 * @param target element - the target input field or division or span 531 * @return object - the associated instance data 532 * @throws error if a jQuery problem getting data 533 */ 534 _getInst: function( target ) { 535 try { 536 return $.data( target, "datepicker" ); 537 } catch ( err ) { 538 throw "Missing instance data for this datepicker"; 539 } 540 }, 541 542 /* Update or retrieve the settings for a date picker attached to an input field or division. 543 * @param target element - the target input field or division or span 544 * @param name object - the new settings to update or 545 * string - the name of the setting to change or retrieve, 546 * when retrieving also "all" for all instance settings or 547 * "defaults" for all global defaults 548 * @param value any - the new value for the setting 549 * (omit if above is an object or to retrieve a value) 550 */ 551 _optionDatepicker: function( target, name, value ) { 552 var settings, date, minDate, maxDate, 553 inst = this._getInst( target ); 554 555 if ( arguments.length === 2 && typeof name === "string" ) { 556 return ( name === "defaults" ? $.extend( {}, $.datepicker._defaults ) : 557 ( inst ? ( name === "all" ? $.extend( {}, inst.settings ) : 558 this._get( inst, name ) ) : null ) ); 559 } 560 561 settings = name || {}; 562 if ( typeof name === "string" ) { 563 settings = {}; 564 settings[ name ] = value; 565 } 566 567 if ( inst ) { 568 if ( this._curInst === inst ) { 569 this._hideDatepicker(); 570 } 571 572 date = this._getDateDatepicker( target, true ); 573 minDate = this._getMinMaxDate( inst, "min" ); 574 maxDate = this._getMinMaxDate( inst, "max" ); 575 datepicker_extendRemove( inst.settings, settings ); 576 577 // reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided 578 if ( minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined ) { 579 inst.settings.minDate = this._formatDate( inst, minDate ); 580 } 581 if ( maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined ) { 582 inst.settings.maxDate = this._formatDate( inst, maxDate ); 583 } 584 if ( "disabled" in settings ) { 585 if ( settings.disabled ) { 586 this._disableDatepicker( target ); 587 } else { 588 this._enableDatepicker( target ); 589 } 590 } 591 this._attachments( $( target ), inst ); 592 this._autoSize( inst ); 593 this._setDate( inst, date ); 594 this._updateAlternate( inst ); 595 this._updateDatepicker( inst ); 596 } 597 }, 598 599 // Change method deprecated 600 _changeDatepicker: function( target, name, value ) { 601 this._optionDatepicker( target, name, value ); 602 }, 603 604 /* Redraw the date picker attached to an input field or division. 605 * @param target element - the target input field or division or span 606 */ 607 _refreshDatepicker: function( target ) { 608 var inst = this._getInst( target ); 609 if ( inst ) { 610 this._updateDatepicker( inst ); 611 } 612 }, 613 614 /* Set the dates for a jQuery selection. 615 * @param target element - the target input field or division or span 616 * @param date Date - the new date 617 */ 618 _setDateDatepicker: function( target, date ) { 619 var inst = this._getInst( target ); 620 if ( inst ) { 621 this._setDate( inst, date ); 622 this._updateDatepicker( inst ); 623 this._updateAlternate( inst ); 624 } 625 }, 626 627 /* Get the date(s) for the first entry in a jQuery selection. 628 * @param target element - the target input field or division or span 629 * @param noDefault boolean - true if no default date is to be used 630 * @return Date - the current date 631 */ 632 _getDateDatepicker: function( target, noDefault ) { 633 var inst = this._getInst( target ); 634 if ( inst && !inst.inline ) { 635 this._setDateFromField( inst, noDefault ); 636 } 637 return ( inst ? this._getDate( inst ) : null ); 638 }, 639 640 /* Handle keystrokes. */ 641 _doKeyDown: function( event ) { 642 var onSelect, dateStr, sel, 643 inst = $.datepicker._getInst( event.target ), 644 handled = true, 645 isRTL = inst.dpDiv.is( ".ui-datepicker-rtl" ); 646 647 inst._keyEvent = true; 648 if ( $.datepicker._datepickerShowing ) { 649 switch ( event.keyCode ) { 650 case 9: $.datepicker._hideDatepicker(); 651 handled = false; 652 break; // hide on tab out 653 case 13: sel = $( "td." + $.datepicker._dayOverClass + ":not(." + 654 $.datepicker._currentClass + ")", inst.dpDiv ); 655 if ( sel[ 0 ] ) { 656 $.datepicker._selectDay( event.target, inst.selectedMonth, inst.selectedYear, sel[ 0 ] ); 657 } 658 659 onSelect = $.datepicker._get( inst, "onSelect" ); 660 if ( onSelect ) { 661 dateStr = $.datepicker._formatDate( inst ); 662 663 // Trigger custom callback 664 onSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] ); 665 } else { 666 $.datepicker._hideDatepicker(); 667 } 668 669 return false; // don't submit the form 670 case 27: $.datepicker._hideDatepicker(); 671 break; // hide on escape 672 case 33: $.datepicker._adjustDate( event.target, ( event.ctrlKey ? 673 -$.datepicker._get( inst, "stepBigMonths" ) : 674 -$.datepicker._get( inst, "stepMonths" ) ), "M" ); 675 break; // previous month/year on page up/+ ctrl 676 case 34: $.datepicker._adjustDate( event.target, ( event.ctrlKey ? 677 +$.datepicker._get( inst, "stepBigMonths" ) : 678 +$.datepicker._get( inst, "stepMonths" ) ), "M" ); 679 break; // next month/year on page down/+ ctrl 680 case 35: if ( event.ctrlKey || event.metaKey ) { 681 $.datepicker._clearDate( event.target ); 682 } 683 handled = event.ctrlKey || event.metaKey; 684 break; // clear on ctrl or command +end 685 case 36: if ( event.ctrlKey || event.metaKey ) { 686 $.datepicker._gotoToday( event.target ); 687 } 688 handled = event.ctrlKey || event.metaKey; 689 break; // current on ctrl or command +home 690 case 37: if ( event.ctrlKey || event.metaKey ) { 691 $.datepicker._adjustDate( event.target, ( isRTL ? +1 : -1 ), "D" ); 692 } 693 handled = event.ctrlKey || event.metaKey; 694 695 // -1 day on ctrl or command +left 696 if ( event.originalEvent.altKey ) { 697 $.datepicker._adjustDate( event.target, ( event.ctrlKey ? 698 -$.datepicker._get( inst, "stepBigMonths" ) : 699 -$.datepicker._get( inst, "stepMonths" ) ), "M" ); 700 } 701 702 // next month/year on alt +left on Mac 703 break; 704 case 38: if ( event.ctrlKey || event.metaKey ) { 705 $.datepicker._adjustDate( event.target, -7, "D" ); 706 } 707 handled = event.ctrlKey || event.metaKey; 708 break; // -1 week on ctrl or command +up 709 case 39: if ( event.ctrlKey || event.metaKey ) { 710 $.datepicker._adjustDate( event.target, ( isRTL ? -1 : +1 ), "D" ); 711 } 712 handled = event.ctrlKey || event.metaKey; 713 714 // +1 day on ctrl or command +right 715 if ( event.originalEvent.altKey ) { 716 $.datepicker._adjustDate( event.target, ( event.ctrlKey ? 717 +$.datepicker._get( inst, "stepBigMonths" ) : 718 +$.datepicker._get( inst, "stepMonths" ) ), "M" ); 719 } 720 721 // next month/year on alt +right 722 break; 723 case 40: if ( event.ctrlKey || event.metaKey ) { 724 $.datepicker._adjustDate( event.target, +7, "D" ); 725 } 726 handled = event.ctrlKey || event.metaKey; 727 break; // +1 week on ctrl or command +down 728 default: handled = false; 729 } 730 } else if ( event.keyCode === 36 && event.ctrlKey ) { // display the date picker on ctrl+home 731 $.datepicker._showDatepicker( this ); 732 } else { 733 handled = false; 734 } 735 736 if ( handled ) { 737 event.preventDefault(); 738 event.stopPropagation(); 739 } 740 }, 741 742 /* Filter entered characters - based on date format. */ 743 _doKeyPress: function( event ) { 744 var chars, chr, 745 inst = $.datepicker._getInst( event.target ); 746 747 if ( $.datepicker._get( inst, "constrainInput" ) ) { 748 chars = $.datepicker._possibleChars( $.datepicker._get( inst, "dateFormat" ) ); 749 chr = String.fromCharCode( event.charCode == null ? event.keyCode : event.charCode ); 750 return event.ctrlKey || event.metaKey || ( chr < " " || !chars || chars.indexOf( chr ) > -1 ); 751 } 752 }, 753 754 /* Synchronise manual entry and field/alternate field. */ 755 _doKeyUp: function( event ) { 756 var date, 757 inst = $.datepicker._getInst( event.target ); 758 759 if ( inst.input.val() !== inst.lastVal ) { 760 try { 761 date = $.datepicker.parseDate( $.datepicker._get( inst, "dateFormat" ), 762 ( inst.input ? inst.input.val() : null ), 763 $.datepicker._getFormatConfig( inst ) ); 764 765 if ( date ) { // only if valid 766 $.datepicker._setDateFromField( inst ); 767 $.datepicker._updateAlternate( inst ); 768 $.datepicker._updateDatepicker( inst ); 769 } 770 } catch ( err ) { 771 } 772 } 773 return true; 774 }, 775 776 /* Pop-up the date picker for a given input field. 777 * If false returned from beforeShow event handler do not show. 778 * @param input element - the input field attached to the date picker or 779 * event - if triggered by focus 780 */ 781 _showDatepicker: function( input ) { 782 input = input.target || input; 783 if ( input.nodeName.toLowerCase() !== "input" ) { // find from button/image trigger 784 input = $( "input", input.parentNode )[ 0 ]; 785 } 786 787 if ( $.datepicker._isDisabledDatepicker( input ) || $.datepicker._lastInput === input ) { // already here 788 return; 789 } 790 791 var inst, beforeShow, beforeShowSettings, isFixed, 792 offset, showAnim, duration; 793 794 inst = $.datepicker._getInst( input ); 795 if ( $.datepicker._curInst && $.datepicker._curInst !== inst ) { 796 $.datepicker._curInst.dpDiv.stop( true, true ); 797 if ( inst && $.datepicker._datepickerShowing ) { 798 $.datepicker._hideDatepicker( $.datepicker._curInst.input[ 0 ] ); 799 } 800 } 801 802 beforeShow = $.datepicker._get( inst, "beforeShow" ); 803 beforeShowSettings = beforeShow ? beforeShow.apply( input, [ input, inst ] ) : {}; 804 if ( beforeShowSettings === false ) { 805 return; 806 } 807 datepicker_extendRemove( inst.settings, beforeShowSettings ); 808 809 inst.lastVal = null; 810 $.datepicker._lastInput = input; 811 $.datepicker._setDateFromField( inst ); 812 813 if ( $.datepicker._inDialog ) { // hide cursor 814 input.value = ""; 815 } 816 if ( !$.datepicker._pos ) { // position below input 817 $.datepicker._pos = $.datepicker._findPos( input ); 818 $.datepicker._pos[ 1 ] += input.offsetHeight; // add the height 819 } 820 821 isFixed = false; 822 $( input ).parents().each( function() { 823 isFixed |= $( this ).css( "position" ) === "fixed"; 824 return !isFixed; 825 } ); 826 827 offset = { left: $.datepicker._pos[ 0 ], top: $.datepicker._pos[ 1 ] }; 828 $.datepicker._pos = null; 829 830 //to avoid flashes on Firefox 831 inst.dpDiv.empty(); 832 833 // determine sizing offscreen 834 inst.dpDiv.css( { position: "absolute", display: "block", top: "-1000px" } ); 835 $.datepicker._updateDatepicker( inst ); 836 837 // fix width for dynamic number of date pickers 838 // and adjust position before showing 839 offset = $.datepicker._checkOffset( inst, offset, isFixed ); 840 inst.dpDiv.css( { position: ( $.datepicker._inDialog && $.blockUI ? 841 "static" : ( isFixed ? "fixed" : "absolute" ) ), display: "none", 842 left: offset.left + "px", top: offset.top + "px" } ); 843 844 if ( !inst.inline ) { 845 showAnim = $.datepicker._get( inst, "showAnim" ); 846 duration = $.datepicker._get( inst, "duration" ); 847 inst.dpDiv.css( "z-index", datepicker_getZindex( $( input ) ) + 1 ); 848 $.datepicker._datepickerShowing = true; 849 850 if ( $.effects && $.effects.effect[ showAnim ] ) { 851 inst.dpDiv.show( showAnim, $.datepicker._get( inst, "showOptions" ), duration ); 852 } else { 853 inst.dpDiv[ showAnim || "show" ]( showAnim ? duration : null ); 854 } 855 856 if ( $.datepicker._shouldFocusInput( inst ) ) { 857 inst.input.trigger( "focus" ); 858 } 859 860 $.datepicker._curInst = inst; 861 } 862 }, 863 864 /* Generate the date picker content. */ 865 _updateDatepicker: function( inst ) { 866 this.maxRows = 4; //Reset the max number of rows being displayed (see #7043) 867 datepicker_instActive = inst; // for delegate hover events 868 inst.dpDiv.empty().append( this._generateHTML( inst ) ); 869 this._attachHandlers( inst ); 870 871 var origyearshtml, 872 numMonths = this._getNumberOfMonths( inst ), 873 cols = numMonths[ 1 ], 874 width = 17, 875 activeCell = inst.dpDiv.find( "." + this._dayOverClass + " a" ), 876 onUpdateDatepicker = $.datepicker._get( inst, "onUpdateDatepicker" ); 877 878 if ( activeCell.length > 0 ) { 879 datepicker_handleMouseover.apply( activeCell.get( 0 ) ); 880 } 881 882 inst.dpDiv.removeClass( "ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4" ).width( "" ); 883 if ( cols > 1 ) { 884 inst.dpDiv.addClass( "ui-datepicker-multi-" + cols ).css( "width", ( width * cols ) + "em" ); 885 } 886 inst.dpDiv[ ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ? "add" : "remove" ) + 887 "Class" ]( "ui-datepicker-multi" ); 888 inst.dpDiv[ ( this._get( inst, "isRTL" ) ? "add" : "remove" ) + 889 "Class" ]( "ui-datepicker-rtl" ); 890 891 if ( inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) { 892 inst.input.trigger( "focus" ); 893 } 894 895 // Deffered render of the years select (to avoid flashes on Firefox) 896 if ( inst.yearshtml ) { 897 origyearshtml = inst.yearshtml; 898 setTimeout( function() { 899 900 //assure that inst.yearshtml didn't change. 901 if ( origyearshtml === inst.yearshtml && inst.yearshtml ) { 902 inst.dpDiv.find( "select.ui-datepicker-year" ).first().replaceWith( inst.yearshtml ); 903 } 904 origyearshtml = inst.yearshtml = null; 905 }, 0 ); 906 } 907 908 if ( onUpdateDatepicker ) { 909 onUpdateDatepicker.apply( ( inst.input ? inst.input[ 0 ] : null ), [ inst ] ); 910 } 911 }, 912 913 // #6694 - don't focus the input if it's already focused 914 // this breaks the change event in IE 915 // Support: IE and jQuery <1.9 916 _shouldFocusInput: function( inst ) { 917 return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" ); 918 }, 919 920 /* Check positioning to remain on screen. */ 921 _checkOffset: function( inst, offset, isFixed ) { 922 var dpWidth = inst.dpDiv.outerWidth(), 923 dpHeight = inst.dpDiv.outerHeight(), 924 inputWidth = inst.input ? inst.input.outerWidth() : 0, 925 inputHeight = inst.input ? inst.input.outerHeight() : 0, 926 viewWidth = document.documentElement.clientWidth + ( isFixed ? 0 : $( document ).scrollLeft() ), 927 viewHeight = document.documentElement.clientHeight + ( isFixed ? 0 : $( document ).scrollTop() ); 928 929 offset.left -= ( this._get( inst, "isRTL" ) ? ( dpWidth - inputWidth ) : 0 ); 930 offset.left -= ( isFixed && offset.left === inst.input.offset().left ) ? $( document ).scrollLeft() : 0; 931 offset.top -= ( isFixed && offset.top === ( inst.input.offset().top + inputHeight ) ) ? $( document ).scrollTop() : 0; 932 933 // Now check if datepicker is showing outside window viewport - move to a better place if so. 934 offset.left -= Math.min( offset.left, ( offset.left + dpWidth > viewWidth && viewWidth > dpWidth ) ? 935 Math.abs( offset.left + dpWidth - viewWidth ) : 0 ); 936 offset.top -= Math.min( offset.top, ( offset.top + dpHeight > viewHeight && viewHeight > dpHeight ) ? 937 Math.abs( dpHeight + inputHeight ) : 0 ); 938 939 return offset; 940 }, 941 942 /* Find an object's position on the screen. */ 943 _findPos: function( obj ) { 944 var position, 945 inst = this._getInst( obj ), 946 isRTL = this._get( inst, "isRTL" ); 947 948 while ( obj && ( obj.type === "hidden" || obj.nodeType !== 1 || $.expr.pseudos.hidden( obj ) ) ) { 949 obj = obj[ isRTL ? "previousSibling" : "nextSibling" ]; 950 } 951 952 position = $( obj ).offset(); 953 return [ position.left, position.top ]; 954 }, 955 956 /* Hide the date picker from view. 957 * @param input element - the input field attached to the date picker 958 */ 959 _hideDatepicker: function( input ) { 960 var showAnim, duration, postProcess, onClose, 961 inst = this._curInst; 962 963 if ( !inst || ( input && inst !== $.data( input, "datepicker" ) ) ) { 964 return; 965 } 966 967 if ( this._datepickerShowing ) { 968 showAnim = this._get( inst, "showAnim" ); 969 duration = this._get( inst, "duration" ); 970 postProcess = function() { 971 $.datepicker._tidyDialog( inst ); 972 }; 973 974 // DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed 975 if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) { 976 inst.dpDiv.hide( showAnim, $.datepicker._get( inst, "showOptions" ), duration, postProcess ); 977 } else { 978 inst.dpDiv[ ( showAnim === "slideDown" ? "slideUp" : 979 ( showAnim === "fadeIn" ? "fadeOut" : "hide" ) ) ]( ( showAnim ? duration : null ), postProcess ); 980 } 981 982 if ( !showAnim ) { 983 postProcess(); 984 } 985 this._datepickerShowing = false; 986 987 onClose = this._get( inst, "onClose" ); 988 if ( onClose ) { 989 onClose.apply( ( inst.input ? inst.input[ 0 ] : null ), [ ( inst.input ? inst.input.val() : "" ), inst ] ); 990 } 991 992 this._lastInput = null; 993 if ( this._inDialog ) { 994 this._dialogInput.css( { position: "absolute", left: "0", top: "-100px" } ); 995 if ( $.blockUI ) { 996 $.unblockUI(); 997 $( "body" ).append( this.dpDiv ); 998 } 999 } 1000 this._inDialog = false; 1001 } 1002 }, 1003 1004 /* Tidy up after a dialog display. */ 1005 _tidyDialog: function( inst ) { 1006 inst.dpDiv.removeClass( this._dialogClass ).off( ".ui-datepicker-calendar" ); 1007 }, 1008 1009 /* Close date picker if clicked elsewhere. */ 1010 _checkExternalClick: function( event ) { 1011 if ( !$.datepicker._curInst ) { 1012 return; 1013 } 1014 1015 var $target = $( event.target ), 1016 inst = $.datepicker._getInst( $target[ 0 ] ); 1017 1018 if ( ( ( $target[ 0 ].id !== $.datepicker._mainDivId && 1019 $target.parents( "#" + $.datepicker._mainDivId ).length === 0 && 1020 !$target.hasClass( $.datepicker.markerClassName ) && 1021 !$target.closest( "." + $.datepicker._triggerClass ).length && 1022 $.datepicker._datepickerShowing && !( $.datepicker._inDialog && $.blockUI ) ) ) || 1023 ( $target.hasClass( $.datepicker.markerClassName ) && $.datepicker._curInst !== inst ) ) { 1024 $.datepicker._hideDatepicker(); 1025 } 1026 }, 1027 1028 /* Adjust one of the date sub-fields. */ 1029 _adjustDate: function( id, offset, period ) { 1030 var target = $( id ), 1031 inst = this._getInst( target[ 0 ] ); 1032 1033 if ( this._isDisabledDatepicker( target[ 0 ] ) ) { 1034 return; 1035 } 1036 this._adjustInstDate( inst, offset, period ); 1037 this._updateDatepicker( inst ); 1038 }, 1039 1040 /* Action for current link. */ 1041 _gotoToday: function( id ) { 1042 var date, 1043 target = $( id ), 1044 inst = this._getInst( target[ 0 ] ); 1045 1046 if ( this._get( inst, "gotoCurrent" ) && inst.currentDay ) { 1047 inst.selectedDay = inst.currentDay; 1048 inst.drawMonth = inst.selectedMonth = inst.currentMonth; 1049 inst.drawYear = inst.selectedYear = inst.currentYear; 1050 } else { 1051 date = new Date(); 1052 inst.selectedDay = date.getDate(); 1053 inst.drawMonth = inst.selectedMonth = date.getMonth(); 1054 inst.drawYear = inst.selectedYear = date.getFullYear(); 1055 } 1056 this._notifyChange( inst ); 1057 this._adjustDate( target ); 1058 }, 1059 1060 /* Action for selecting a new month/year. */ 1061 _selectMonthYear: function( id, select, period ) { 1062 var target = $( id ), 1063 inst = this._getInst( target[ 0 ] ); 1064 1065 inst[ "selected" + ( period === "M" ? "Month" : "Year" ) ] = 1066 inst[ "draw" + ( period === "M" ? "Month" : "Year" ) ] = 1067 parseInt( select.options[ select.selectedIndex ].value, 10 ); 1068 1069 this._notifyChange( inst ); 1070 this._adjustDate( target ); 1071 }, 1072 1073 /* Action for selecting a day. */ 1074 _selectDay: function( id, month, year, td ) { 1075 var inst, 1076 target = $( id ); 1077 1078 if ( $( td ).hasClass( this._unselectableClass ) || this._isDisabledDatepicker( target[ 0 ] ) ) { 1079 return; 1080 } 1081 1082 inst = this._getInst( target[ 0 ] ); 1083 inst.selectedDay = inst.currentDay = parseInt( $( "a", td ).attr( "data-date" ) ); 1084 inst.selectedMonth = inst.currentMonth = month; 1085 inst.selectedYear = inst.currentYear = year; 1086 this._selectDate( id, this._formatDate( inst, 1087 inst.currentDay, inst.currentMonth, inst.currentYear ) ); 1088 }, 1089 1090 /* Erase the input field and hide the date picker. */ 1091 _clearDate: function( id ) { 1092 var target = $( id ); 1093 this._selectDate( target, "" ); 1094 }, 1095 1096 /* Update the input field with the selected date. */ 1097 _selectDate: function( id, dateStr ) { 1098 var onSelect, 1099 target = $( id ), 1100 inst = this._getInst( target[ 0 ] ); 1101 1102 dateStr = ( dateStr != null ? dateStr : this._formatDate( inst ) ); 1103 if ( inst.input ) { 1104 inst.input.val( dateStr ); 1105 } 1106 this._updateAlternate( inst ); 1107 1108 onSelect = this._get( inst, "onSelect" ); 1109 if ( onSelect ) { 1110 onSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] ); // trigger custom callback 1111 } else if ( inst.input ) { 1112 inst.input.trigger( "change" ); // fire the change event 1113 } 1114 1115 if ( inst.inline ) { 1116 this._updateDatepicker( inst ); 1117 } else { 1118 this._hideDatepicker(); 1119 this._lastInput = inst.input[ 0 ]; 1120 if ( typeof( inst.input[ 0 ] ) !== "object" ) { 1121 inst.input.trigger( "focus" ); // restore focus 1122 } 1123 this._lastInput = null; 1124 } 1125 }, 1126 1127 /* Update any alternate field to synchronise with the main field. */ 1128 _updateAlternate: function( inst ) { 1129 var altFormat, date, dateStr, 1130 altField = this._get( inst, "altField" ); 1131 1132 if ( altField ) { // update alternate field too 1133 altFormat = this._get( inst, "altFormat" ) || this._get( inst, "dateFormat" ); 1134 date = this._getDate( inst ); 1135 dateStr = this.formatDate( altFormat, date, this._getFormatConfig( inst ) ); 1136 $( document ).find( altField ).val( dateStr ); 1137 } 1138 }, 1139 1140 /* Set as beforeShowDay function to prevent selection of weekends. 1141 * @param date Date - the date to customise 1142 * @return [boolean, string] - is this date selectable?, what is its CSS class? 1143 */ 1144 noWeekends: function( date ) { 1145 var day = date.getDay(); 1146 return [ ( day > 0 && day < 6 ), "" ]; 1147 }, 1148 1149 /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. 1150 * @param date Date - the date to get the week for 1151 * @return number - the number of the week within the year that contains this date 1152 */ 1153 iso8601Week: function( date ) { 1154 var time, 1155 checkDate = new Date( date.getTime() ); 1156 1157 // Find Thursday of this week starting on Monday 1158 checkDate.setDate( checkDate.getDate() + 4 - ( checkDate.getDay() || 7 ) ); 1159 1160 time = checkDate.getTime(); 1161 checkDate.setMonth( 0 ); // Compare with Jan 1 1162 checkDate.setDate( 1 ); 1163 return Math.floor( Math.round( ( time - checkDate ) / 86400000 ) / 7 ) + 1; 1164 }, 1165 1166 /* Parse a string value into a date object. 1167 * See formatDate below for the possible formats. 1168 * 1169 * @param format string - the expected format of the date 1170 * @param value string - the date in the above format 1171 * @param settings Object - attributes include: 1172 * shortYearCutoff number - the cutoff year for determining the century (optional) 1173 * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) 1174 * dayNames string[7] - names of the days from Sunday (optional) 1175 * monthNamesShort string[12] - abbreviated names of the months (optional) 1176 * monthNames string[12] - names of the months (optional) 1177 * @return Date - the extracted date value or null if value is blank 1178 */ 1179 parseDate: function( format, value, settings ) { 1180 if ( format == null || value == null ) { 1181 throw "Invalid arguments"; 1182 } 1183 1184 value = ( typeof value === "object" ? value.toString() : value + "" ); 1185 if ( value === "" ) { 1186 return null; 1187 } 1188 1189 var iFormat, dim, extra, 1190 iValue = 0, 1191 shortYearCutoffTemp = ( settings ? settings.shortYearCutoff : null ) || this._defaults.shortYearCutoff, 1192 shortYearCutoff = ( typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp : 1193 new Date().getFullYear() % 100 + parseInt( shortYearCutoffTemp, 10 ) ), 1194 dayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort, 1195 dayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames, 1196 monthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort, 1197 monthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames, 1198 year = -1, 1199 month = -1, 1200 day = -1, 1201 doy = -1, 1202 literal = false, 1203 date, 1204 1205 // Check whether a format character is doubled 1206 lookAhead = function( match ) { 1207 var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match ); 1208 if ( matches ) { 1209 iFormat++; 1210 } 1211 return matches; 1212 }, 1213 1214 // Extract a number from the string value 1215 getNumber = function( match ) { 1216 var isDoubled = lookAhead( match ), 1217 size = ( match === "@" ? 14 : ( match === "!" ? 20 : 1218 ( match === "y" && isDoubled ? 4 : ( match === "o" ? 3 : 2 ) ) ) ), 1219 minSize = ( match === "y" ? size : 1 ), 1220 digits = new RegExp( "^\\d{" + minSize + "," + size + "}" ), 1221 num = value.substring( iValue ).match( digits ); 1222 if ( !num ) { 1223 throw "Missing number at position " + iValue; 1224 } 1225 iValue += num[ 0 ].length; 1226 return parseInt( num[ 0 ], 10 ); 1227 }, 1228 1229 // Extract a name from the string value and convert to an index 1230 getName = function( match, shortNames, longNames ) { 1231 var index = -1, 1232 names = $.map( lookAhead( match ) ? longNames : shortNames, function( v, k ) { 1233 return [ [ k, v ] ]; 1234 } ).sort( function( a, b ) { 1235 return -( a[ 1 ].length - b[ 1 ].length ); 1236 } ); 1237 1238 $.each( names, function( i, pair ) { 1239 var name = pair[ 1 ]; 1240 if ( value.substr( iValue, name.length ).toLowerCase() === name.toLowerCase() ) { 1241 index = pair[ 0 ]; 1242 iValue += name.length; 1243 return false; 1244 } 1245 } ); 1246 if ( index !== -1 ) { 1247 return index + 1; 1248 } else { 1249 throw "Unknown name at position " + iValue; 1250 } 1251 }, 1252 1253 // Confirm that a literal character matches the string value 1254 checkLiteral = function() { 1255 if ( value.charAt( iValue ) !== format.charAt( iFormat ) ) { 1256 throw "Unexpected literal at position " + iValue; 1257 } 1258 iValue++; 1259 }; 1260 1261 for ( iFormat = 0; iFormat < format.length; iFormat++ ) { 1262 if ( literal ) { 1263 if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) { 1264 literal = false; 1265 } else { 1266 checkLiteral(); 1267 } 1268 } else { 1269 switch ( format.charAt( iFormat ) ) { 1270 case "d": 1271 day = getNumber( "d" ); 1272 break; 1273 case "D": 1274 getName( "D", dayNamesShort, dayNames ); 1275 break; 1276 case "o": 1277 doy = getNumber( "o" ); 1278 break; 1279 case "m": 1280 month = getNumber( "m" ); 1281 break; 1282 case "M": 1283 month = getName( "M", monthNamesShort, monthNames ); 1284 break; 1285 case "y": 1286 year = getNumber( "y" ); 1287 break; 1288 case "@": 1289 date = new Date( getNumber( "@" ) ); 1290 year = date.getFullYear(); 1291 month = date.getMonth() + 1; 1292 day = date.getDate(); 1293 break; 1294 case "!": 1295 date = new Date( ( getNumber( "!" ) - this._ticksTo1970 ) / 10000 ); 1296 year = date.getFullYear(); 1297 month = date.getMonth() + 1; 1298 day = date.getDate(); 1299 break; 1300 case "'": 1301 if ( lookAhead( "'" ) ) { 1302 checkLiteral(); 1303 } else { 1304 literal = true; 1305 } 1306 break; 1307 default: 1308 checkLiteral(); 1309 } 1310 } 1311 } 1312 1313 if ( iValue < value.length ) { 1314 extra = value.substr( iValue ); 1315 if ( !/^\s+/.test( extra ) ) { 1316 throw "Extra/unparsed characters found in date: " + extra; 1317 } 1318 } 1319 1320 if ( year === -1 ) { 1321 year = new Date().getFullYear(); 1322 } else if ( year < 100 ) { 1323 year += new Date().getFullYear() - new Date().getFullYear() % 100 + 1324 ( year <= shortYearCutoff ? 0 : -100 ); 1325 } 1326 1327 if ( doy > -1 ) { 1328 month = 1; 1329 day = doy; 1330 do { 1331 dim = this._getDaysInMonth( year, month - 1 ); 1332 if ( day <= dim ) { 1333 break; 1334 } 1335 month++; 1336 day -= dim; 1337 } while ( true ); 1338 } 1339 1340 date = this._daylightSavingAdjust( new Date( year, month - 1, day ) ); 1341 if ( date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day ) { 1342 throw "Invalid date"; // E.g. 31/02/00 1343 } 1344 return date; 1345 }, 1346 1347 /* Standard date formats. */ 1348 ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601) 1349 COOKIE: "D, dd M yy", 1350 ISO_8601: "yy-mm-dd", 1351 RFC_822: "D, d M y", 1352 RFC_850: "DD, dd-M-y", 1353 RFC_1036: "D, d M y", 1354 RFC_1123: "D, d M yy", 1355 RFC_2822: "D, d M yy", 1356 RSS: "D, d M y", // RFC 822 1357 TICKS: "!", 1358 TIMESTAMP: "@", 1359 W3C: "yy-mm-dd", // ISO 8601 1360 1361 _ticksTo1970: ( ( ( 1970 - 1 ) * 365 + Math.floor( 1970 / 4 ) - Math.floor( 1970 / 100 ) + 1362 Math.floor( 1970 / 400 ) ) * 24 * 60 * 60 * 10000000 ), 1363 1364 /* Format a date object into a string value. 1365 * The format can be combinations of the following: 1366 * d - day of month (no leading zero) 1367 * dd - day of month (two digit) 1368 * o - day of year (no leading zeros) 1369 * oo - day of year (three digit) 1370 * D - day name short 1371 * DD - day name long 1372 * m - month of year (no leading zero) 1373 * mm - month of year (two digit) 1374 * M - month name short 1375 * MM - month name long 1376 * y - year (two digit) 1377 * yy - year (four digit) 1378 * @ - Unix timestamp (ms since 01/01/1970) 1379 * ! - Windows ticks (100ns since 01/01/0001) 1380 * "..." - literal text 1381 * '' - single quote 1382 * 1383 * @param format string - the desired format of the date 1384 * @param date Date - the date value to format 1385 * @param settings Object - attributes include: 1386 * dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) 1387 * dayNames string[7] - names of the days from Sunday (optional) 1388 * monthNamesShort string[12] - abbreviated names of the months (optional) 1389 * monthNames string[12] - names of the months (optional) 1390 * @return string - the date in the above format 1391 */ 1392 formatDate: function( format, date, settings ) { 1393 if ( !date ) { 1394 return ""; 1395 } 1396 1397 var iFormat, 1398 dayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort, 1399 dayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames, 1400 monthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort, 1401 monthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames, 1402 1403 // Check whether a format character is doubled 1404 lookAhead = function( match ) { 1405 var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match ); 1406 if ( matches ) { 1407 iFormat++; 1408 } 1409 return matches; 1410 }, 1411 1412 // Format a number, with leading zero if necessary 1413 formatNumber = function( match, value, len ) { 1414 var num = "" + value; 1415 if ( lookAhead( match ) ) { 1416 while ( num.length < len ) { 1417 num = "0" + num; 1418 } 1419 } 1420 return num; 1421 }, 1422 1423 // Format a name, short or long as requested 1424 formatName = function( match, value, shortNames, longNames ) { 1425 return ( lookAhead( match ) ? longNames[ value ] : shortNames[ value ] ); 1426 }, 1427 output = "", 1428 literal = false; 1429 1430 if ( date ) { 1431 for ( iFormat = 0; iFormat < format.length; iFormat++ ) { 1432 if ( literal ) { 1433 if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) { 1434 literal = false; 1435 } else { 1436 output += format.charAt( iFormat ); 1437 } 1438 } else { 1439 switch ( format.charAt( iFormat ) ) { 1440 case "d": 1441 output += formatNumber( "d", date.getDate(), 2 ); 1442 break; 1443 case "D": 1444 output += formatName( "D", date.getDay(), dayNamesShort, dayNames ); 1445 break; 1446 case "o": 1447 output += formatNumber( "o", 1448 Math.round( ( new Date( date.getFullYear(), date.getMonth(), date.getDate() ).getTime() - new Date( date.getFullYear(), 0, 0 ).getTime() ) / 86400000 ), 3 ); 1449 break; 1450 case "m": 1451 output += formatNumber( "m", date.getMonth() + 1, 2 ); 1452 break; 1453 case "M": 1454 output += formatName( "M", date.getMonth(), monthNamesShort, monthNames ); 1455 break; 1456 case "y": 1457 output += ( lookAhead( "y" ) ? date.getFullYear() : 1458 ( date.getFullYear() % 100 < 10 ? "0" : "" ) + date.getFullYear() % 100 ); 1459 break; 1460 case "@": 1461 output += date.getTime(); 1462 break; 1463 case "!": 1464 output += date.getTime() * 10000 + this._ticksTo1970; 1465 break; 1466 case "'": 1467 if ( lookAhead( "'" ) ) { 1468 output += "'"; 1469 } else { 1470 literal = true; 1471 } 1472 break; 1473 default: 1474 output += format.charAt( iFormat ); 1475 } 1476 } 1477 } 1478 } 1479 return output; 1480 }, 1481 1482 /* Extract all possible characters from the date format. */ 1483 _possibleChars: function( format ) { 1484 var iFormat, 1485 chars = "", 1486 literal = false, 1487 1488 // Check whether a format character is doubled 1489 lookAhead = function( match ) { 1490 var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match ); 1491 if ( matches ) { 1492 iFormat++; 1493 } 1494 return matches; 1495 }; 1496 1497 for ( iFormat = 0; iFormat < format.length; iFormat++ ) { 1498 if ( literal ) { 1499 if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) { 1500 literal = false; 1501 } else { 1502 chars += format.charAt( iFormat ); 1503 } 1504 } else { 1505 switch ( format.charAt( iFormat ) ) { 1506 case "d": case "m": case "y": case "@": 1507 chars += "0123456789"; 1508 break; 1509 case "D": case "M": 1510 return null; // Accept anything 1511 case "'": 1512 if ( lookAhead( "'" ) ) { 1513 chars += "'"; 1514 } else { 1515 literal = true; 1516 } 1517 break; 1518 default: 1519 chars += format.charAt( iFormat ); 1520 } 1521 } 1522 } 1523 return chars; 1524 }, 1525 1526 /* Get a setting value, defaulting if necessary. */ 1527 _get: function( inst, name ) { 1528 return inst.settings[ name ] !== undefined ? 1529 inst.settings[ name ] : this._defaults[ name ]; 1530 }, 1531 1532 /* Parse existing date and initialise date picker. */ 1533 _setDateFromField: function( inst, noDefault ) { 1534 if ( inst.input.val() === inst.lastVal ) { 1535 return; 1536 } 1537 1538 var dateFormat = this._get( inst, "dateFormat" ), 1539 dates = inst.lastVal = inst.input ? inst.input.val() : null, 1540 defaultDate = this._getDefaultDate( inst ), 1541 date = defaultDate, 1542 settings = this._getFormatConfig( inst ); 1543 1544 try { 1545 date = this.parseDate( dateFormat, dates, settings ) || defaultDate; 1546 } catch ( event ) { 1547 dates = ( noDefault ? "" : dates ); 1548 } 1549 inst.selectedDay = date.getDate(); 1550 inst.drawMonth = inst.selectedMonth = date.getMonth(); 1551 inst.drawYear = inst.selectedYear = date.getFullYear(); 1552 inst.currentDay = ( dates ? date.getDate() : 0 ); 1553 inst.currentMonth = ( dates ? date.getMonth() : 0 ); 1554 inst.currentYear = ( dates ? date.getFullYear() : 0 ); 1555 this._adjustInstDate( inst ); 1556 }, 1557 1558 /* Retrieve the default date shown on opening. */ 1559 _getDefaultDate: function( inst ) { 1560 return this._restrictMinMax( inst, 1561 this._determineDate( inst, this._get( inst, "defaultDate" ), new Date() ) ); 1562 }, 1563 1564 /* A date may be specified as an exact value or a relative one. */ 1565 _determineDate: function( inst, date, defaultDate ) { 1566 var offsetNumeric = function( offset ) { 1567 var date = new Date(); 1568 date.setDate( date.getDate() + offset ); 1569 return date; 1570 }, 1571 offsetString = function( offset ) { 1572 try { 1573 return $.datepicker.parseDate( $.datepicker._get( inst, "dateFormat" ), 1574 offset, $.datepicker._getFormatConfig( inst ) ); 1575 } catch ( e ) { 1576 1577 // Ignore 1578 } 1579 1580 var date = ( offset.toLowerCase().match( /^c/ ) ? 1581 $.datepicker._getDate( inst ) : null ) || new Date(), 1582 year = date.getFullYear(), 1583 month = date.getMonth(), 1584 day = date.getDate(), 1585 pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g, 1586 matches = pattern.exec( offset ); 1587 1588 while ( matches ) { 1589 switch ( matches[ 2 ] || "d" ) { 1590 case "d" : case "D" : 1591 day += parseInt( matches[ 1 ], 10 ); break; 1592 case "w" : case "W" : 1593 day += parseInt( matches[ 1 ], 10 ) * 7; break; 1594 case "m" : case "M" : 1595 month += parseInt( matches[ 1 ], 10 ); 1596 day = Math.min( day, $.datepicker._getDaysInMonth( year, month ) ); 1597 break; 1598 case "y": case "Y" : 1599 year += parseInt( matches[ 1 ], 10 ); 1600 day = Math.min( day, $.datepicker._getDaysInMonth( year, month ) ); 1601 break; 1602 } 1603 matches = pattern.exec( offset ); 1604 } 1605 return new Date( year, month, day ); 1606 }, 1607 newDate = ( date == null || date === "" ? defaultDate : ( typeof date === "string" ? offsetString( date ) : 1608 ( typeof date === "number" ? ( isNaN( date ) ? defaultDate : offsetNumeric( date ) ) : new Date( date.getTime() ) ) ) ); 1609 1610 newDate = ( newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate ); 1611 if ( newDate ) { 1612 newDate.setHours( 0 ); 1613 newDate.setMinutes( 0 ); 1614 newDate.setSeconds( 0 ); 1615 newDate.setMilliseconds( 0 ); 1616 } 1617 return this._daylightSavingAdjust( newDate ); 1618 }, 1619 1620 /* Handle switch to/from daylight saving. 1621 * Hours may be non-zero on daylight saving cut-over: 1622 * > 12 when midnight changeover, but then cannot generate 1623 * midnight datetime, so jump to 1AM, otherwise reset. 1624 * @param date (Date) the date to check 1625 * @return (Date) the corrected date 1626 */ 1627 _daylightSavingAdjust: function( date ) { 1628 if ( !date ) { 1629 return null; 1630 } 1631 date.setHours( date.getHours() > 12 ? date.getHours() + 2 : 0 ); 1632 return date; 1633 }, 1634 1635 /* Set the date(s) directly. */ 1636 _setDate: function( inst, date, noChange ) { 1637 var clear = !date, 1638 origMonth = inst.selectedMonth, 1639 origYear = inst.selectedYear, 1640 newDate = this._restrictMinMax( inst, this._determineDate( inst, date, new Date() ) ); 1641 1642 inst.selectedDay = inst.currentDay = newDate.getDate(); 1643 inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth(); 1644 inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear(); 1645 if ( ( origMonth !== inst.selectedMonth || origYear !== inst.selectedYear ) && !noChange ) { 1646 this._notifyChange( inst ); 1647 } 1648 this._adjustInstDate( inst ); 1649 if ( inst.input ) { 1650 inst.input.val( clear ? "" : this._formatDate( inst ) ); 1651 } 1652 }, 1653 1654 /* Retrieve the date(s) directly. */ 1655 _getDate: function( inst ) { 1656 var startDate = ( !inst.currentYear || ( inst.input && inst.input.val() === "" ) ? null : 1657 this._daylightSavingAdjust( new Date( 1658 inst.currentYear, inst.currentMonth, inst.currentDay ) ) ); 1659 return startDate; 1660 }, 1661 1662 /* Attach the onxxx handlers. These are declared statically so 1663 * they work with static code transformers like Caja. 1664 */ 1665 _attachHandlers: function( inst ) { 1666 var stepMonths = this._get( inst, "stepMonths" ), 1667 id = "#" + inst.id.replace( /\\\\/g, "\\" ); 1668 inst.dpDiv.find( "[data-handler]" ).map( function() { 1669 var handler = { 1670 prev: function() { 1671 $.datepicker._adjustDate( id, -stepMonths, "M" ); 1672 }, 1673 next: function() { 1674 $.datepicker._adjustDate( id, +stepMonths, "M" ); 1675 }, 1676 hide: function() { 1677 $.datepicker._hideDatepicker(); 1678 }, 1679 today: function() { 1680 $.datepicker._gotoToday( id ); 1681 }, 1682 selectDay: function() { 1683 $.datepicker._selectDay( id, +this.getAttribute( "data-month" ), +this.getAttribute( "data-year" ), this ); 1684 return false; 1685 }, 1686 selectMonth: function() { 1687 $.datepicker._selectMonthYear( id, this, "M" ); 1688 return false; 1689 }, 1690 selectYear: function() { 1691 $.datepicker._selectMonthYear( id, this, "Y" ); 1692 return false; 1693 } 1694 }; 1695 $( this ).on( this.getAttribute( "data-event" ), handler[ this.getAttribute( "data-handler" ) ] ); 1696 } ); 1697 }, 1698 1699 /* Generate the HTML for the current state of the date picker. */ 1700 _generateHTML: function( inst ) { 1701 var maxDraw, prevText, prev, nextText, next, currentText, gotoDate, 1702 controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin, 1703 monthNames, monthNamesShort, beforeShowDay, showOtherMonths, 1704 selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate, 1705 cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows, 1706 printDate, dRow, tbody, daySettings, otherMonth, unselectable, 1707 tempDate = new Date(), 1708 today = this._daylightSavingAdjust( 1709 new Date( tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate() ) ), // clear time 1710 isRTL = this._get( inst, "isRTL" ), 1711 showButtonPanel = this._get( inst, "showButtonPanel" ), 1712 hideIfNoPrevNext = this._get( inst, "hideIfNoPrevNext" ), 1713 navigationAsDateFormat = this._get( inst, "navigationAsDateFormat" ), 1714 numMonths = this._getNumberOfMonths( inst ), 1715 showCurrentAtPos = this._get( inst, "showCurrentAtPos" ), 1716 stepMonths = this._get( inst, "stepMonths" ), 1717 isMultiMonth = ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ), 1718 currentDate = this._daylightSavingAdjust( ( !inst.currentDay ? new Date( 9999, 9, 9 ) : 1719 new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ), 1720 minDate = this._getMinMaxDate( inst, "min" ), 1721 maxDate = this._getMinMaxDate( inst, "max" ), 1722 drawMonth = inst.drawMonth - showCurrentAtPos, 1723 drawYear = inst.drawYear; 1724 1725 if ( drawMonth < 0 ) { 1726 drawMonth += 12; 1727 drawYear--; 1728 } 1729 if ( maxDate ) { 1730 maxDraw = this._daylightSavingAdjust( new Date( maxDate.getFullYear(), 1731 maxDate.getMonth() - ( numMonths[ 0 ] * numMonths[ 1 ] ) + 1, maxDate.getDate() ) ); 1732 maxDraw = ( minDate && maxDraw < minDate ? minDate : maxDraw ); 1733 while ( this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 ) ) > maxDraw ) { 1734 drawMonth--; 1735 if ( drawMonth < 0 ) { 1736 drawMonth = 11; 1737 drawYear--; 1738 } 1739 } 1740 } 1741 inst.drawMonth = drawMonth; 1742 inst.drawYear = drawYear; 1743 1744 prevText = this._get( inst, "prevText" ); 1745 prevText = ( !navigationAsDateFormat ? prevText : this.formatDate( prevText, 1746 this._daylightSavingAdjust( new Date( drawYear, drawMonth - stepMonths, 1 ) ), 1747 this._getFormatConfig( inst ) ) ); 1748 1749 if ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ) { 1750 prev = $( "<a>" ) 1751 .attr( { 1752 "class": "ui-datepicker-prev ui-corner-all", 1753 "data-handler": "prev", 1754 "data-event": "click", 1755 title: prevText 1756 } ) 1757 .append( 1758 $( "<span>" ) 1759 .addClass( "ui-icon ui-icon-circle-triangle-" + 1760 ( isRTL ? "e" : "w" ) ) 1761 .text( prevText ) 1762 )[ 0 ].outerHTML; 1763 } else if ( hideIfNoPrevNext ) { 1764 prev = ""; 1765 } else { 1766 prev = $( "<a>" ) 1767 .attr( { 1768 "class": "ui-datepicker-prev ui-corner-all ui-state-disabled", 1769 title: prevText 1770 } ) 1771 .append( 1772 $( "<span>" ) 1773 .addClass( "ui-icon ui-icon-circle-triangle-" + 1774 ( isRTL ? "e" : "w" ) ) 1775 .text( prevText ) 1776 )[ 0 ].outerHTML; 1777 } 1778 1779 nextText = this._get( inst, "nextText" ); 1780 nextText = ( !navigationAsDateFormat ? nextText : this.formatDate( nextText, 1781 this._daylightSavingAdjust( new Date( drawYear, drawMonth + stepMonths, 1 ) ), 1782 this._getFormatConfig( inst ) ) ); 1783 1784 if ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ) { 1785 next = $( "<a>" ) 1786 .attr( { 1787 "class": "ui-datepicker-next ui-corner-all", 1788 "data-handler": "next", 1789 "data-event": "click", 1790 title: nextText 1791 } ) 1792 .append( 1793 $( "<span>" ) 1794 .addClass( "ui-icon ui-icon-circle-triangle-" + 1795 ( isRTL ? "w" : "e" ) ) 1796 .text( nextText ) 1797 )[ 0 ].outerHTML; 1798 } else if ( hideIfNoPrevNext ) { 1799 next = ""; 1800 } else { 1801 next = $( "<a>" ) 1802 .attr( { 1803 "class": "ui-datepicker-next ui-corner-all ui-state-disabled", 1804 title: nextText 1805 } ) 1806 .append( 1807 $( "<span>" ) 1808 .attr( "class", "ui-icon ui-icon-circle-triangle-" + 1809 ( isRTL ? "w" : "e" ) ) 1810 .text( nextText ) 1811 )[ 0 ].outerHTML; 1812 } 1813 1814 currentText = this._get( inst, "currentText" ); 1815 gotoDate = ( this._get( inst, "gotoCurrent" ) && inst.currentDay ? currentDate : today ); 1816 currentText = ( !navigationAsDateFormat ? currentText : 1817 this.formatDate( currentText, gotoDate, this._getFormatConfig( inst ) ) ); 1818 1819 controls = ""; 1820 if ( !inst.inline ) { 1821 controls = $( "<button>" ) 1822 .attr( { 1823 type: "button", 1824 "class": "ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all", 1825 "data-handler": "hide", 1826 "data-event": "click" 1827 } ) 1828 .text( this._get( inst, "closeText" ) )[ 0 ].outerHTML; 1829 } 1830 1831 buttonPanel = ""; 1832 if ( showButtonPanel ) { 1833 buttonPanel = $( "<div class='ui-datepicker-buttonpane ui-widget-content'>" ) 1834 .append( isRTL ? controls : "" ) 1835 .append( this._isInRange( inst, gotoDate ) ? 1836 $( "<button>" ) 1837 .attr( { 1838 type: "button", 1839 "class": "ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all", 1840 "data-handler": "today", 1841 "data-event": "click" 1842 } ) 1843 .text( currentText ) : 1844 "" ) 1845 .append( isRTL ? "" : controls )[ 0 ].outerHTML; 1846 } 1847 1848 firstDay = parseInt( this._get( inst, "firstDay" ), 10 ); 1849 firstDay = ( isNaN( firstDay ) ? 0 : firstDay ); 1850 1851 showWeek = this._get( inst, "showWeek" ); 1852 dayNames = this._get( inst, "dayNames" ); 1853 dayNamesMin = this._get( inst, "dayNamesMin" ); 1854 monthNames = this._get( inst, "monthNames" ); 1855 monthNamesShort = this._get( inst, "monthNamesShort" ); 1856 beforeShowDay = this._get( inst, "beforeShowDay" ); 1857 showOtherMonths = this._get( inst, "showOtherMonths" ); 1858 selectOtherMonths = this._get( inst, "selectOtherMonths" ); 1859 defaultDate = this._getDefaultDate( inst ); 1860 html = ""; 1861 1862 for ( row = 0; row < numMonths[ 0 ]; row++ ) { 1863 group = ""; 1864 this.maxRows = 4; 1865 for ( col = 0; col < numMonths[ 1 ]; col++ ) { 1866 selectedDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, inst.selectedDay ) ); 1867 cornerClass = " ui-corner-all"; 1868 calender = ""; 1869 if ( isMultiMonth ) { 1870 calender += "<div class='ui-datepicker-group"; 1871 if ( numMonths[ 1 ] > 1 ) { 1872 switch ( col ) { 1873 case 0: calender += " ui-datepicker-group-first"; 1874 cornerClass = " ui-corner-" + ( isRTL ? "right" : "left" ); break; 1875 case numMonths[ 1 ] - 1: calender += " ui-datepicker-group-last"; 1876 cornerClass = " ui-corner-" + ( isRTL ? "left" : "right" ); break; 1877 default: calender += " ui-datepicker-group-middle"; cornerClass = ""; break; 1878 } 1879 } 1880 calender += "'>"; 1881 } 1882 calender += "<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix" + cornerClass + "'>" + 1883 ( /all|left/.test( cornerClass ) && row === 0 ? ( isRTL ? next : prev ) : "" ) + 1884 ( /all|right/.test( cornerClass ) && row === 0 ? ( isRTL ? prev : next ) : "" ) + 1885 this._generateMonthYearHeader( inst, drawMonth, drawYear, minDate, maxDate, 1886 row > 0 || col > 0, monthNames, monthNamesShort ) + // draw month headers 1887 "</div><table class='ui-datepicker-calendar'><thead>" + 1888 "<tr>"; 1889 thead = ( showWeek ? "<th class='ui-datepicker-week-col'>" + this._get( inst, "weekHeader" ) + "</th>" : "" ); 1890 for ( dow = 0; dow < 7; dow++ ) { // days of the week 1891 day = ( dow + firstDay ) % 7; 1892 thead += "<th scope='col'" + ( ( dow + firstDay + 6 ) % 7 >= 5 ? " class='ui-datepicker-week-end'" : "" ) + ">" + 1893 "<span title='" + dayNames[ day ] + "'>" + dayNamesMin[ day ] + "</span></th>"; 1894 } 1895 calender += thead + "</tr></thead><tbody>"; 1896 daysInMonth = this._getDaysInMonth( drawYear, drawMonth ); 1897 if ( drawYear === inst.selectedYear && drawMonth === inst.selectedMonth ) { 1898 inst.selectedDay = Math.min( inst.selectedDay, daysInMonth ); 1899 } 1900 leadDays = ( this._getFirstDayOfMonth( drawYear, drawMonth ) - firstDay + 7 ) % 7; 1901 curRows = Math.ceil( ( leadDays + daysInMonth ) / 7 ); // calculate the number of rows to generate 1902 numRows = ( isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows ); //If multiple months, use the higher number of rows (see #7043) 1903 this.maxRows = numRows; 1904 printDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 - leadDays ) ); 1905 for ( dRow = 0; dRow < numRows; dRow++ ) { // create date picker rows 1906 calender += "<tr>"; 1907 tbody = ( !showWeek ? "" : "<td class='ui-datepicker-week-col'>" + 1908 this._get( inst, "calculateWeek" )( printDate ) + "</td>" ); 1909 for ( dow = 0; dow < 7; dow++ ) { // create date picker days 1910 daySettings = ( beforeShowDay ? 1911 beforeShowDay.apply( ( inst.input ? inst.input[ 0 ] : null ), [ printDate ] ) : [ true, "" ] ); 1912 otherMonth = ( printDate.getMonth() !== drawMonth ); 1913 unselectable = ( otherMonth && !selectOtherMonths ) || !daySettings[ 0 ] || 1914 ( minDate && printDate < minDate ) || ( maxDate && printDate > maxDate ); 1915 tbody += "<td class='" + 1916 ( ( dow + firstDay + 6 ) % 7 >= 5 ? " ui-datepicker-week-end" : "" ) + // highlight weekends 1917 ( otherMonth ? " ui-datepicker-other-month" : "" ) + // highlight days from other months 1918 ( ( printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent ) || // user pressed key 1919 ( defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime() ) ? 1920 1921 // or defaultDate is current printedDate and defaultDate is selectedDate 1922 " " + this._dayOverClass : "" ) + // highlight selected day 1923 ( unselectable ? " " + this._unselectableClass + " ui-state-disabled" : "" ) + // highlight unselectable days 1924 ( otherMonth && !showOtherMonths ? "" : " " + daySettings[ 1 ] + // highlight custom dates 1925 ( printDate.getTime() === currentDate.getTime() ? " " + this._currentClass : "" ) + // highlight selected day 1926 ( printDate.getTime() === today.getTime() ? " ui-datepicker-today" : "" ) ) + "'" + // highlight today (if different) 1927 ( ( !otherMonth || showOtherMonths ) && daySettings[ 2 ] ? " title='" + daySettings[ 2 ].replace( /'/g, "'" ) + "'" : "" ) + // cell title 1928 ( unselectable ? "" : " data-handler='selectDay' data-event='click' data-month='" + printDate.getMonth() + "' data-year='" + printDate.getFullYear() + "'" ) + ">" + // actions 1929 ( otherMonth && !showOtherMonths ? " " : // display for other months 1930 ( unselectable ? "<span class='ui-state-default'>" + printDate.getDate() + "</span>" : "<a class='ui-state-default" + 1931 ( printDate.getTime() === today.getTime() ? " ui-state-highlight" : "" ) + 1932 ( printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "" ) + // highlight selected day 1933 ( otherMonth ? " ui-priority-secondary" : "" ) + // distinguish dates from other months 1934 "' href='#' aria-current='" + ( printDate.getTime() === currentDate.getTime() ? "true" : "false" ) + // mark date as selected for screen reader 1935 "' data-date='" + printDate.getDate() + // store date as data 1936 "'>" + printDate.getDate() + "</a>" ) ) + "</td>"; // display selectable date 1937 printDate.setDate( printDate.getDate() + 1 ); 1938 printDate = this._daylightSavingAdjust( printDate ); 1939 } 1940 calender += tbody + "</tr>"; 1941 } 1942 drawMonth++; 1943 if ( drawMonth > 11 ) { 1944 drawMonth = 0; 1945 drawYear++; 1946 } 1947 calender += "</tbody></table>" + ( isMultiMonth ? "</div>" + 1948 ( ( numMonths[ 0 ] > 0 && col === numMonths[ 1 ] - 1 ) ? "<div class='ui-datepicker-row-break'></div>" : "" ) : "" ); 1949 group += calender; 1950 } 1951 html += group; 1952 } 1953 html += buttonPanel; 1954 inst._keyEvent = false; 1955 return html; 1956 }, 1957 1958 /* Generate the month and year header. */ 1959 _generateMonthYearHeader: function( inst, drawMonth, drawYear, minDate, maxDate, 1960 secondary, monthNames, monthNamesShort ) { 1961 1962 var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear, 1963 changeMonth = this._get( inst, "changeMonth" ), 1964 changeYear = this._get( inst, "changeYear" ), 1965 showMonthAfterYear = this._get( inst, "showMonthAfterYear" ), 1966 selectMonthLabel = this._get( inst, "selectMonthLabel" ), 1967 selectYearLabel = this._get( inst, "selectYearLabel" ), 1968 html = "<div class='ui-datepicker-title'>", 1969 monthHtml = ""; 1970 1971 // Month selection 1972 if ( secondary || !changeMonth ) { 1973 monthHtml += "<span class='ui-datepicker-month'>" + monthNames[ drawMonth ] + "</span>"; 1974 } else { 1975 inMinYear = ( minDate && minDate.getFullYear() === drawYear ); 1976 inMaxYear = ( maxDate && maxDate.getFullYear() === drawYear ); 1977 monthHtml += "<select class='ui-datepicker-month' aria-label='" + selectMonthLabel + "' data-handler='selectMonth' data-event='change'>"; 1978 for ( month = 0; month < 12; month++ ) { 1979 if ( ( !inMinYear || month >= minDate.getMonth() ) && ( !inMaxYear || month <= maxDate.getMonth() ) ) { 1980 monthHtml += "<option value='" + month + "'" + 1981 ( month === drawMonth ? " selected='selected'" : "" ) + 1982 ">" + monthNamesShort[ month ] + "</option>"; 1983 } 1984 } 1985 monthHtml += "</select>"; 1986 } 1987 1988 if ( !showMonthAfterYear ) { 1989 html += monthHtml + ( secondary || !( changeMonth && changeYear ) ? " " : "" ); 1990 } 1991 1992 // Year selection 1993 if ( !inst.yearshtml ) { 1994 inst.yearshtml = ""; 1995 if ( secondary || !changeYear ) { 1996 html += "<span class='ui-datepicker-year'>" + drawYear + "</span>"; 1997 } else { 1998 1999 // determine range of years to display 2000 years = this._get( inst, "yearRange" ).split( ":" ); 2001 thisYear = new Date().getFullYear(); 2002 determineYear = function( value ) { 2003 var year = ( value.match( /c[+\-].*/ ) ? drawYear + parseInt( value.substring( 1 ), 10 ) : 2004 ( value.match( /[+\-].*/ ) ? thisYear + parseInt( value, 10 ) : 2005 parseInt( value, 10 ) ) ); 2006 return ( isNaN( year ) ? thisYear : year ); 2007 }; 2008 year = determineYear( years[ 0 ] ); 2009 endYear = Math.max( year, determineYear( years[ 1 ] || "" ) ); 2010 year = ( minDate ? Math.max( year, minDate.getFullYear() ) : year ); 2011 endYear = ( maxDate ? Math.min( endYear, maxDate.getFullYear() ) : endYear ); 2012 inst.yearshtml += "<select class='ui-datepicker-year' aria-label='" + selectYearLabel + "' data-handler='selectYear' data-event='change'>"; 2013 for ( ; year <= endYear; year++ ) { 2014 inst.yearshtml += "<option value='" + year + "'" + 2015 ( year === drawYear ? " selected='selected'" : "" ) + 2016 ">" + year + "</option>"; 2017 } 2018 inst.yearshtml += "</select>"; 2019 2020 html += inst.yearshtml; 2021 inst.yearshtml = null; 2022 } 2023 } 2024 2025 html += this._get( inst, "yearSuffix" ); 2026 if ( showMonthAfterYear ) { 2027 html += ( secondary || !( changeMonth && changeYear ) ? " " : "" ) + monthHtml; 2028 } 2029 html += "</div>"; // Close datepicker_header 2030 return html; 2031 }, 2032 2033 /* Adjust one of the date sub-fields. */ 2034 _adjustInstDate: function( inst, offset, period ) { 2035 var year = inst.selectedYear + ( period === "Y" ? offset : 0 ), 2036 month = inst.selectedMonth + ( period === "M" ? offset : 0 ), 2037 day = Math.min( inst.selectedDay, this._getDaysInMonth( year, month ) ) + ( period === "D" ? offset : 0 ), 2038 date = this._restrictMinMax( inst, this._daylightSavingAdjust( new Date( year, month, day ) ) ); 2039 2040 inst.selectedDay = date.getDate(); 2041 inst.drawMonth = inst.selectedMonth = date.getMonth(); 2042 inst.drawYear = inst.selectedYear = date.getFullYear(); 2043 if ( period === "M" || period === "Y" ) { 2044 this._notifyChange( inst ); 2045 } 2046 }, 2047 2048 /* Ensure a date is within any min/max bounds. */ 2049 _restrictMinMax: function( inst, date ) { 2050 var minDate = this._getMinMaxDate( inst, "min" ), 2051 maxDate = this._getMinMaxDate( inst, "max" ), 2052 newDate = ( minDate && date < minDate ? minDate : date ); 2053 return ( maxDate && newDate > maxDate ? maxDate : newDate ); 2054 }, 2055 2056 /* Notify change of month/year. */ 2057 _notifyChange: function( inst ) { 2058 var onChange = this._get( inst, "onChangeMonthYear" ); 2059 if ( onChange ) { 2060 onChange.apply( ( inst.input ? inst.input[ 0 ] : null ), 2061 [ inst.selectedYear, inst.selectedMonth + 1, inst ] ); 2062 } 2063 }, 2064 2065 /* Determine the number of months to show. */ 2066 _getNumberOfMonths: function( inst ) { 2067 var numMonths = this._get( inst, "numberOfMonths" ); 2068 return ( numMonths == null ? [ 1, 1 ] : ( typeof numMonths === "number" ? [ 1, numMonths ] : numMonths ) ); 2069 }, 2070 2071 /* Determine the current maximum date - ensure no time components are set. */ 2072 _getMinMaxDate: function( inst, minMax ) { 2073 return this._determineDate( inst, this._get( inst, minMax + "Date" ), null ); 2074 }, 2075 2076 /* Find the number of days in a given month. */ 2077 _getDaysInMonth: function( year, month ) { 2078 return 32 - this._daylightSavingAdjust( new Date( year, month, 32 ) ).getDate(); 2079 }, 2080 2081 /* Find the day of the week of the first of a month. */ 2082 _getFirstDayOfMonth: function( year, month ) { 2083 return new Date( year, month, 1 ).getDay(); 2084 }, 2085 2086 /* Determines if we should allow a "next/prev" month display change. */ 2087 _canAdjustMonth: function( inst, offset, curYear, curMonth ) { 2088 var numMonths = this._getNumberOfMonths( inst ), 2089 date = this._daylightSavingAdjust( new Date( curYear, 2090 curMonth + ( offset < 0 ? offset : numMonths[ 0 ] * numMonths[ 1 ] ), 1 ) ); 2091 2092 if ( offset < 0 ) { 2093 date.setDate( this._getDaysInMonth( date.getFullYear(), date.getMonth() ) ); 2094 } 2095 return this._isInRange( inst, date ); 2096 }, 2097 2098 /* Is the given date in the accepted range? */ 2099 _isInRange: function( inst, date ) { 2100 var yearSplit, currentYear, 2101 minDate = this._getMinMaxDate( inst, "min" ), 2102 maxDate = this._getMinMaxDate( inst, "max" ), 2103 minYear = null, 2104 maxYear = null, 2105 years = this._get( inst, "yearRange" ); 2106 if ( years ) { 2107 yearSplit = years.split( ":" ); 2108 currentYear = new Date().getFullYear(); 2109 minYear = parseInt( yearSplit[ 0 ], 10 ); 2110 maxYear = parseInt( yearSplit[ 1 ], 10 ); 2111 if ( yearSplit[ 0 ].match( /[+\-].*/ ) ) { 2112 minYear += currentYear; 2113 } 2114 if ( yearSplit[ 1 ].match( /[+\-].*/ ) ) { 2115 maxYear += currentYear; 2116 } 2117 } 2118 2119 return ( ( !minDate || date.getTime() >= minDate.getTime() ) && 2120 ( !maxDate || date.getTime() <= maxDate.getTime() ) && 2121 ( !minYear || date.getFullYear() >= minYear ) && 2122 ( !maxYear || date.getFullYear() <= maxYear ) ); 2123 }, 2124 2125 /* Provide the configuration settings for formatting/parsing. */ 2126 _getFormatConfig: function( inst ) { 2127 var shortYearCutoff = this._get( inst, "shortYearCutoff" ); 2128 shortYearCutoff = ( typeof shortYearCutoff !== "string" ? shortYearCutoff : 2129 new Date().getFullYear() % 100 + parseInt( shortYearCutoff, 10 ) ); 2130 return { shortYearCutoff: shortYearCutoff, 2131 dayNamesShort: this._get( inst, "dayNamesShort" ), dayNames: this._get( inst, "dayNames" ), 2132 monthNamesShort: this._get( inst, "monthNamesShort" ), monthNames: this._get( inst, "monthNames" ) }; 2133 }, 2134 2135 /* Format the given date for display. */ 2136 _formatDate: function( inst, day, month, year ) { 2137 if ( !day ) { 2138 inst.currentDay = inst.selectedDay; 2139 inst.currentMonth = inst.selectedMonth; 2140 inst.currentYear = inst.selectedYear; 2141 } 2142 var date = ( day ? ( typeof day === "object" ? day : 2143 this._daylightSavingAdjust( new Date( year, month, day ) ) ) : 2144 this._daylightSavingAdjust( new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ); 2145 return this.formatDate( this._get( inst, "dateFormat" ), date, this._getFormatConfig( inst ) ); 2146 } 2147 } ); 2148 2149 /* 2150 * Bind hover events for datepicker elements. 2151 * Done via delegate so the binding only occurs once in the lifetime of the parent div. 2152 * Global datepicker_instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker. 2153 */ 2154 function datepicker_bindHover( dpDiv ) { 2155 var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a"; 2156 return dpDiv.on( "mouseout", selector, function() { 2157 $( this ).removeClass( "ui-state-hover" ); 2158 if ( this.className.indexOf( "ui-datepicker-prev" ) !== -1 ) { 2159 $( this ).removeClass( "ui-datepicker-prev-hover" ); 2160 } 2161 if ( this.className.indexOf( "ui-datepicker-next" ) !== -1 ) { 2162 $( this ).removeClass( "ui-datepicker-next-hover" ); 2163 } 2164 } ) 2165 .on( "mouseover", selector, datepicker_handleMouseover ); 2166 } 2167 2168 function datepicker_handleMouseover() { 2169 if ( !$.datepicker._isDisabledDatepicker( datepicker_instActive.inline ? datepicker_instActive.dpDiv.parent()[ 0 ] : datepicker_instActive.input[ 0 ] ) ) { 2170 $( this ).parents( ".ui-datepicker-calendar" ).find( "a" ).removeClass( "ui-state-hover" ); 2171 $( this ).addClass( "ui-state-hover" ); 2172 if ( this.className.indexOf( "ui-datepicker-prev" ) !== -1 ) { 2173 $( this ).addClass( "ui-datepicker-prev-hover" ); 2174 } 2175 if ( this.className.indexOf( "ui-datepicker-next" ) !== -1 ) { 2176 $( this ).addClass( "ui-datepicker-next-hover" ); 2177 } 2178 } 2179 } 2180 2181 /* jQuery extend now ignores nulls! */ 2182 function datepicker_extendRemove( target, props ) { 2183 $.extend( target, props ); 2184 for ( var name in props ) { 2185 if ( props[ name ] == null ) { 2186 target[ name ] = props[ name ]; 2187 } 2188 } 2189 return target; 2190 } 2191 2192 /* Invoke the datepicker functionality. 2193 @param options string - a command, optionally followed by additional parameters or 2194 Object - settings for attaching new datepicker functionality 2195 @return jQuery object */ 2196 $.fn.datepicker = function( options ) { 2197 2198 /* Verify an empty collection wasn't passed - Fixes #6976 */ 2199 if ( !this.length ) { 2200 return this; 2201 } 2202 2203 /* Initialise the date picker. */ 2204 if ( !$.datepicker.initialized ) { 2205 $( document ).on( "mousedown", $.datepicker._checkExternalClick ); 2206 $.datepicker.initialized = true; 2207 } 2208 2209 /* Append datepicker main container to body if not exist. */ 2210 if ( $( "#" + $.datepicker._mainDivId ).length === 0 ) { 2211 $( "body" ).append( $.datepicker.dpDiv ); 2212 } 2213 2214 var otherArgs = Array.prototype.slice.call( arguments, 1 ); 2215 if ( typeof options === "string" && ( options === "isDisabled" || options === "getDate" || options === "widget" ) ) { 2216 return $.datepicker[ "_" + options + "Datepicker" ]. 2217 apply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) ); 2218 } 2219 if ( options === "option" && arguments.length === 2 && typeof arguments[ 1 ] === "string" ) { 2220 return $.datepicker[ "_" + options + "Datepicker" ]. 2221 apply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) ); 2222 } 2223 return this.each( function() { 2224 if ( typeof options === "string" ) { 2225 $.datepicker[ "_" + options + "Datepicker" ] 2226 .apply( $.datepicker, [ this ].concat( otherArgs ) ); 2227 } else { 2228 $.datepicker._attachDatepicker( this, options ); 2229 } 2230 } ); 2231 }; 2232 2233 $.datepicker = new Datepicker(); // singleton instance 2234 $.datepicker.initialized = false; 2235 $.datepicker.uuid = new Date().getTime(); 2236 $.datepicker.version = "1.13.3"; 2237 2238 return $.datepicker; 2239 2240 } );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Wed Dec 25 08:20:01 2024 | Cross-referenced by PHPXref |