1/* Simple JavaScript Inheritance 2 * By John Resig http://ejohn.org/ 3 * MIT Licensed. 4 */ 5// Inspired by base2 and Prototype 6(function(){ 7 var initializing = false; 8 9 // The base JQClass implementation (does nothing) 10 window.JQClass = function(){}; 11 12 // Collection of derived classes 13 JQClass.classes = {}; 14 15 // Create a new JQClass that inherits from this class 16 JQClass.extend = function extender(prop) { 17 var base = this.prototype; 18 19 // Instantiate a base class (but only create the instance, 20 // don't run the init constructor) 21 initializing = true; 22 var prototype = new this(); 23 initializing = false; 24 25 // Copy the properties over onto the new prototype 26 for (var name in prop) { 27 // Check if we're overwriting an existing function 28 prototype[name] = typeof prop[name] == 'function' && 29 typeof base[name] == 'function' ? 30 (function(name, fn){ 31 return function() { 32 var __super = this._super; 33 34 // Add a new ._super() method that is the same method 35 // but on the super-class 36 this._super = function(args) { 37 return base[name].apply(this, args || []); 38 }; 39 40 var ret = fn.apply(this, arguments); 41 42 // The method only need to be bound temporarily, so we 43 // remove it when we're done executing 44 this._super = __super; 45 46 return ret; 47 }; 48 })(name, prop[name]) : 49 prop[name]; 50 } 51 52 // The dummy class constructor 53 function JQClass() { 54 // All construction is actually done in the init method 55 if (!initializing && this._init) { 56 this._init.apply(this, arguments); 57 } 58 } 59 60 // Populate our constructed prototype object 61 JQClass.prototype = prototype; 62 63 // Enforce the constructor to be what we expect 64 JQClass.prototype.constructor = JQClass; 65 66 // And make this class extendable 67 JQClass.extend = extender; 68 69 return JQClass; 70 }; 71})(); 72 73(function($) { // Ensure $, encapsulate 74 75 /** Abstract base class for collection plugins v1.0.1. 76 Written by Keith Wood (kbwood{at}iinet.com.au) December 2013. 77 Licensed under the MIT (https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) license. 78 @module $.JQPlugin 79 @abstract */ 80 JQClass.classes.JQPlugin = JQClass.extend({ 81 82 /** Name to identify this plugin. 83 @example name: 'tabs' */ 84 name: 'plugin', 85 86 /** Default options for instances of this plugin (default: {}). 87 @example defaultOptions: { 88 selectedClass: 'selected', 89 triggers: 'click' 90 } */ 91 defaultOptions: {}, 92 93 /** Options dependent on the locale. 94 Indexed by language and (optional) country code, with '' denoting the default language (English/US). 95 @example regionalOptions: { 96 '': { 97 greeting: 'Hi' 98 } 99 } */ 100 regionalOptions: {}, 101 102 /** Names of getter methods - those that can't be chained (default: []). 103 @example _getters: ['activeTab'] */ 104 _getters: [], 105 106 /** Retrieve a marker class for affected elements. 107 @private 108 @return {string} The marker class. */ 109 _getMarker: function() { 110 return 'is-' + this.name; 111 }, 112 113 /** Initialise the plugin. 114 Create the jQuery bridge - plugin name <code>xyz</code> 115 produces <code>$.xyz</code> and <code>$.fn.xyz</code>. */ 116 _init: function() { 117 // Apply default localisations 118 $.extend(this.defaultOptions, (this.regionalOptions && this.regionalOptions['']) || {}); 119 // Camel-case the name 120 var jqName = camelCase(this.name); 121 // Expose jQuery singleton manager 122 $[jqName] = this; 123 // Expose jQuery collection plugin 124 $.fn[jqName] = function(options) { 125 var otherArgs = Array.prototype.slice.call(arguments, 1); 126 if ($[jqName]._isNotChained(options, otherArgs)) { 127 return $[jqName][options].apply($[jqName], [this[0]].concat(otherArgs)); 128 } 129 return this.each(function() { 130 if (typeof options === 'string') { 131 if (options[0] === '_' || !$[jqName][options]) { 132 throw 'Unknown method: ' + options; 133 } 134 $[jqName][options].apply($[jqName], [this].concat(otherArgs)); 135 } 136 else { 137 $[jqName]._attach(this, options); 138 } 139 }); 140 }; 141 }, 142 143 /** Set default values for all subsequent instances. 144 @param options {object} The new default options. 145 @example $.plugin.setDefauls({name: value}) */ 146 setDefaults: function(options) { 147 $.extend(this.defaultOptions, options || {}); 148 }, 149 150 /** Determine whether a method is a getter and doesn't permit chaining. 151 @private 152 @param name {string}
152 The method name. 153 @param otherArgs {any[]} Any other arguments for the method. 154 @return {boolean} True if this method is a getter, false otherwise. */ 155 _isNotChained: function(name, otherArgs) { 156 if (name === 'option' && (otherArgs.length === 0 || 157 (otherArgs.length === 1 && typeof otherArgs[0] === 'string'))) { 158 return true; 159 } 160 return $.inArray(name, this._getters) > -1; 161 }, 162 163 /** Initialise an element. Called internally only. 164 Adds an instance object as data named for the plugin. 165 @param elem {Element} The element to enhance. 166 @param options {object} Overriding settings. */ 167 _attach: function(elem, options) { 168 elem = $(elem); 169 if (elem.hasClass(this._getMarker())) { 170 return; 171 } 172 elem.addClass(this._getMarker()); 173 options = $.extend({}, this.defaultOptions, this._getMetadata(elem), options || {}); 174 var inst = $.extend({name: this.name, elem: elem, options: options}, 175 this._instSettings(elem, options)); 176 elem.data(this.name, inst); // Save instance against element 177 this._postAttach(elem, inst); 178 this.option(elem, options); 179 }, 180 181 /** Retrieve additional instance settings. 182 Override this in a sub-class to provide extra settings. 183 @param elem {jQuery} The current jQuery element. 184 @param options {object} The instance options. 185 @return {object} Any extra instance values. 186 @example _instSettings: function(elem, options) { 187 return {nav: elem.find(options.navSelector)}; 188 } */ 189 _instSettings: function(elem, options) { 190 return {}; 191 }, 192 193 /** Plugin specific post initialisation. 194 Override this in a sub-class to perform extra activities. 195 @param elem {jQuery} The current jQuery element. 196 @param inst {object} The instance settings. 197 @example _postAttach: function(elem, inst) { 198 elem.on('click.' + this.name, function() { 199 ... 200 }); 201 } */ 202 _postAttach: function(elem, inst) { 203 }, 204 205 /** Retrieve metadata configuration from the element. 206 Metadata is specified as an attribute: 207 <code>data-<plugin name>="<setting name>: '<value>', ..."</code>. 208 Dates should be specified as strings in this format: 'new Date(y, m-1, d)'. 209 @private 210 @param elem {jQuery} The source element. 211 @return {object} The inline configuration or {}. */ 212 _getMetadata: function(elem) { 213 try { 214 var data = elem.data(this.name.toLowerCase()) || ''; 215 data = data.replace(/'/g, '"'); 216 data = data.replace(/([a-zA-Z0-9]+):/g, function(match, group, i) { 217 var count = data.substring(0, i).match(/"/g); // Handle embedded ':' 218 return (!count || count.length % 2 === 0 ? '"' + group + '":' : group + ':'); 219 }); 220 data = $.parseJSON('{' + data + '}'); 221 for (var name in data) { // Convert dates 222 var value = data[name]; 223 if (typeof value === 'string' && value.match(/^new Date\((.*)\)$/)) { 224 data[name] = eval(value); 225 } 226 } 227 return data; 228 } 229 catch (e) { 230 return {}; 231 } 232 }, 233 234 /** Retrieve the instance data for element. 235 @param elem {Element} The source element. 236 @return {object} The instance data or {}. */ 237 _getInst: function(elem) { 238 return $(elem).data(this.name) || {}; 239 }, 240 241 /** Retrieve or reconfigure the settings for a plugin. 242 @param elem {Element} The source element. 243 @param name {object|string} The collection of new option values or the name of a single option. 244 @param [value] {any} The value for a single named option. 245 @return {any|object} If retrieving a single value or all options. 246 @example $(selector).plugin('option', 'name', value) 247 $(selector).plugin('option', {name: value, ...}) 248 var value = $(selector).plugin('option', 'name') 249 var options = $(selector).plugin('option') */ 250 option: function(elem, name, value) { 251 elem = $(elem); 252 var inst = elem.data(this.name); 253 if (!name || (typeof name === 'string' && value == null)) { 254 var options = (inst || {}).options; 255 return (options && name ? options[name] : options); 256 } 257 if (!elem.hasClass(this._getMarker())) { 258 return; 259 } 260 var options = name || {}; 261 if (typeof name === 'string') { 262 options = {}; 263 options[name] = value; 264 } 265 this._optionsChanged(elem, inst, options); 266 $.extend(inst.options, options); 267 }, 268 269 /** Plugin specific options processing. 270 Old value available in <code>inst.options[name]</code>, new value in <code>options[name]</code>. 271 Override this in a sub-class to perform extra activities. 272 @param elem {jQuery} The current jQuery element. 273 @param inst {object} The instance settings. 274 @param options {object} The new options. 275 @example _optionsChanged: function(elem, inst, options) { 276 if (options.name != inst.options.name) { 277 elem.removeClass(inst.options.name).addClass(options.name); 278 } 279 } */ 280 _optionsChanged: function(elem, inst, options) { 281 }, 282 283 /** Remove all trace of the plugin. 284 Override <code>_preDestroy</code> for plugin-specific processing. 285 @param elem {Element} The source element. 286 @example $(selector).plugin('destroy') */ 287 destroy: function(elem) { 288 elem = $(elem); 289 if (!elem.hasClass(this._getMarker())) { 290 return; 291 } 292 this._preDestroy(elem, this._getInst(elem)); 293 elem.removeData(this.name).removeClass(this._getMarker()); 294 }, 295 296 /** Plugin specific pre destruction. 297 Override this in a sub-class to perform extra activities and undo everything that was 298 done in the <code>_postAttach</code> or <code>_optionsChanged</code> functions. 299 @param elem {jQuery} The current jQuery element. 300 @param inst {object} The instance settings. 301 @example _preDestroy: function(elem, inst) { 302 elem.off('.' + this.name); 303 } */ 304 _preDestroy: function(elem, inst) { 305 } 306 }); 307 308 /** Convert names from hyphenated to camel-case. 309 @private 310 @param value {string} The original hyphenated name. 311 @return {string} The camel-case version. */ 312 function camelCase(name) { 313 return name.replace(/-([a-z])/g, function(match, group) { 314 return group.toUpperCase(); 315 }); 316 } 317 318 /** Expose the plugin base. 319 @namespace "$.JQPlugin" */ 320 $.JQPlugin = { 321 322 /** Create a new collection plugin. 323 @memberof "$.JQPlugin"
324 @param [superClass='JQPlugin'] {string} The name of the parent class to inherit from. 325 @param overrides {object} The property/function overrides for the new class. 326 @example $.JQPlugin.createPlugin({ 327 name: 'tabs', 328 defaultOptions: {selectedClass: 'selected'}, 329 _initSettings: function(elem, options) { return {...}; }, 330 _postAttach: function(elem, inst) { ... } 331 }); */ 332 createPlugin: function(superClass, overrides) { 333 if (typeof superClass === 'object') { 334 overrides = superClass; 335 superClass = 'JQPlugin'; 336 } 337 superClass = camelCase(superClass); 338 var className = camelCase(overrides.name); 339 JQClass.classes[className] = JQClass.classes[superClass].extend(overrides); 340 new JQClass.classes[className](); 341 } 342 }; 343 344})(jQuery); 345 346 347/* http://keith-wood.name/datepick.html 348 Date picker for jQuery v5.0.0. 349 Written by Keith Wood (kbwood{at}iinet.com.au) February 2010. 350 Licensed under the MIT (https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) licence. 351 Please attribute the author if you use it. */ 352 353(function($) { // Hide scope, no $ conflict 354 355 var pluginName = 'datepick'; 356 357 358 /** Create the datepicker plugin. 359 <p>Sets an input field to popup a calendar for date entry, 360 or a <code>div</code> or <code>span</code> to show an inline calendar.</p> 361 <p>Expects HTML like:</p> 362 <pre><input type="text"> or <div></div></pre> 363 <p>Provide inline configuration like:</p> 364 <pre><input type="text" data-datepick="name: 'value'"/></pre> 365 @module Datepick 366 @augments JQPlugin 367 @example $(selector).datepick() 368 $(selector).datepick({minDate: 0, maxDate: '+1m +1w'}) */ 369 $.JQPlugin.createPlugin({ 370 371 /** The name of the plugin. */ 372 name: pluginName, 373 374 /** Default template for generating a datepicker. 375 Insert anywhere: '{l10n:name}' to insert localised value for name, 376 '{link:name}' to insert a link trigger for command name, 377 '{button:name}' to insert a button trigger for command name, 378 '{popup:start}...{popup:end}' to mark a section for inclusion in a popup datepicker only, 379 '{inline:start}...{inline:end}' to mark a section for inclusion in an inline datepicker only. 380 @property picker {string} Overall structure: '{months}' to insert calendar months. 381 @property monthRow {string} One row of months: '{months}' to insert calendar months. 382 @property month {string} A single month: '{monthHeader<em>:dateFormat</em>}' to insert the month header - 383 <em>dateFormat</em> is optional and defaults to 'MM yyyy', 384 '{weekHeader}' to insert a week header, '{weeks}' to insert the month's weeks. 385 @property weekHeader {string} A week header: '{days}' to insert individual day names. 386 @property dayHeader {string} Individual day header: '{day}' to insert day name. 387 @property week {string} One week of the month: '{days}' to insert the week's days, 388 '{weekOfYear}' to insert week of year. 389 @property day {string} An individual day: '{day}' to insert day value. 390 @property monthSelector {string} jQuery selector, relative to picker, for a single month. 391 @property daySelector {string} jQuery selector, relative to picker, for individual days. 392 @property rtlClass {string} Class for right-to-left (RTL) languages. 393 @property multiClass {string} Class for multi-month datepickers. 394 @property defaultClass {string} Class for selectable dates.
395 @property selectedClass {string} Class for currently selected dates. 396 @property highlightedClass {string} Class for highlighted dates. 397 @property todayClass {string} Class for today. 398 @property otherMonthClass {string} Class for days from other months. 399 @property weekendClass {string} Class for days on weekends. 400 @property commandClass {string} Class prefix for commands. 401 @property commandButtonClass {string} Extra class(es) for commands that are buttons. 402 @property commandLinkClass {string} Extra class(es) for commands that are links. 403 @property disabledClass {string} Class for disabled commands. */ 404 defaultRenderer: { 405 picker: '<div class="datepick">' + 406 '<div class="datepick-nav">{link:prev}{link:today}{link:next}</div>{months}' + 407 '{popup:start}<div class="datepick-ctrl">{link:clear}{link:close}</div>{popup:end}' + 408 '<div class="datepick-clear-fix"></div></div>', 409 monthRow: '<div class="datepick-month-row">{months}</div>', 410 month: '<div class="datepick-month"><div class="datepick-month-header">{monthHeader}</div>' + 411 '<table><thead>{weekHeader}</thead><tbody>{weeks}</tbody></table></div>', 412 weekHeader: '<tr>{days}</tr>', 413 dayHeader: '<th>{day}</th>', 414 week: '<tr>{days}</tr>', 415 day: '<td>{day}</td>', 416 monthSelector: '.datepick-month', 417 daySelector: 'td', 418 rtlClass: 'datepick-rtl', 419 multiClass: 'datepick-multi', 420 defaultClass: '', 421 selectedClass: 'datepick-selected', 422 highlightedClass: 'datepick-highlight', 423 todayClass: 'datepick-today', 424 otherMonthClass: 'datepick-other-month', 425 weekendClass: 'datepick-weekend', 426 commandClass: 'datepick-cmd', 427 commandButtonClass: '', 428 commandLinkClass: '', 429 disabledClass: 'datepick-disabled' 430 }, 431 432 /** Command actions that may be added to a layout by name. 433 <ul> 434 <li>prev - Show the previous month (based on <code>monthsToStep</code> option) - <em>PageUp</em></li> 435 <li>prevJump - Show the previous year (based on <code>monthsToJump</code> option) - <em>Ctrl+PageUp</em></li> 436 <li>next - Show the next month (based on <code>monthsToStep</code> option) - <em>PageDown</em></li> 437 <li>nextJump - Show the next year (based on <code>monthsToJump</code> option) - <em>Ctrl+PageDown</em></li> 438 <li>current - Show the currently selected month or today's if none selected - <em>Ctrl+Home</em></li> 439 <li>today - Show today's month - <em>Ctrl+Home</em></li> 440 <li>clear - Erase the date and close the datepicker popup - <em>Ctrl+End</em></li> 441 <li>close - Close the datepicker popup - <em>Esc</em></li> 442 <li>prevWeek - Move the cursor to the previous week - <em>Ctrl+Up</em></li> 443 <li>prevDay - Move the cursor to the previous day - <em>Ctrl+Left</em></li> 444 <li>nextDay - Move the cursor to the next day - <em>Ctrl+Right</em></li> 445 <li>nextWeek - Move the cursor to the next week - <em>Ctrl+Down</em></li> 446 </ul> 447 The command name is the key name and is used to add the command to a layout 448 with '{button:name}' or '{link:name}'. Each has the following attributes. 449 @property text {string} The field in the regional settings for the displayed text. 450 @property status {string} The field in the regional settings for the status text. 451 @property keystroke {object} The keystroke to trigger the action, with attributes: 452 <code>keyCode</code> {number} the code for the keystroke, 453 <code>ctrlKey</code> {boolean} <code>true</code> if <em>Ctrl</em> is required, 454 <code>altKey</code> {boolean} <code>true</code> if <em>Alt</em> is required, 455 <code>shiftKey</code> {boolean} <code>true</code> if <em>Shift</em> is required. 456 @property enabled {DatepickCommandEnabled} The function that indicates the command is enabled. 457 @property date {DatepickCommandDate} The function to get the date associated with this action. 458 @property action {DatepickCommandAction} The function that implements the action. */ 459 commands: { 460 prev: {text: 'prevText', status: 'prevStatus', // Previous month 461 keystroke: {keyCode: 33}, // Page up 462 enabled: function(inst) { 463 var minDate = inst.curMinDate(); 464 return (!minDate || plugin.add(plugin.day(
465 plugin._applyMonthsOffset(plugin.add(plugin.newDate(inst.drawDate), 466 1 - inst.options.monthsToStep, 'm'), inst), 1), -1, 'd'). 467 getTime() >= minDate.getTime()); }, 468 date: function(inst) { 469 return plugin.day(plugin._applyMonthsOffset(plugin.add( 470 plugin.newDate(inst.drawDate), -inst.options.monthsToStep, 'm'), inst), 1); }, 471 action: function(inst) { 472 plugin.changeMonth(this, -inst.options.monthsToStep); } 473 }, 474 prevJump: {text: 'prevJumpText', status: 'prevJumpStatus', // Previous year 475 keystroke: {keyCode: 33, ctrlKey: true}, // Ctrl + Page up 476 enabled: function(inst) { 477 var minDate = inst.curMinDate(); 478 return (!minDate || plugin.add(plugin.day( 479 plugin._applyMonthsOffset(plugin.add(plugin.newDate(inst.drawDate), 480 1 - inst.options.monthsToJump, 'm'), inst), 1), -1, 'd'). 481 getTime() >= minDate.getTime()); }, 482 date: function(inst) { 483 return plugin.day(plugin._applyMonthsOffset(plugin.add( 484 plugin.newDate(inst.drawDate), -inst.options.monthsToJump, 'm'), inst), 1); }, 485 action: function(inst) { 486 plugin.changeMonth(this, -inst.options.monthsToJump); } 487 }, 488 next: {text: 'nextText', status: 'nextStatus', // Next month 489 keystroke: {keyCode: 34}, // Page down 490 enabled: function(inst) { 491 var maxDate = inst.get('maxDate'); 492 return (!maxDate || plugin.day(plugin._applyMonthsOffset(plugin.add( 493 plugin.newDate(inst.drawDate), inst.options.monthsToStep, 'm'), inst), 1). 494 getTime() <= maxDate.getTime()); }, 495 date: function(inst) { 496 return plugin.day(plugin._applyMonthsOffset(plugin.add( 497 plugin.newDate(inst.drawDate), inst.options.monthsToStep, 'm'), inst), 1); }, 498 action: function(inst) { 499 plugin.changeMonth(this, inst.options.monthsToStep); } 500 }, 501 nextJump: {text: 'nextJumpText', status: 'nextJumpStatus', // Next year 502 keystroke: {keyCode: 34, ctrlKey: true}, // Ctrl + Page down 503 enabled: function(inst) { 504 var maxDate = inst.get('maxDate'); 505 return (!maxDate || plugin.day(plugin._applyMonthsOffset(plugin.add( 506 plugin.newDate(inst.drawDate), inst.options.monthsToJump, 'm'), inst), 1). 507 getTime() <= maxDate.getTime()); }, 508 date: function(inst) { 509 return plugin.day(plugin._applyMonthsOffset(plugin.add( 510 plugin.newDate(inst.drawDate), inst.options.monthsToJump, 'm'), inst), 1); }, 511 action: function(inst) { 512 plugin.changeMonth(this, inst.options.monthsToJump); } 513 }, 514 current: {text: 'currentText', status: 'currentStatus', // Current month 515 keystroke: {keyCode: 36, ctrlKey: true}, // Ctrl + Home 516 enabled: function(inst) { 517 var minDate = inst.curMinDate(); 518 var maxDate = inst.get('maxDate'); 519 var curDate = inst.selectedDates[0] || plugin.today(); 520 return (!minDate || curDate.getTime() >= minDate.getTime()) && 521 (!maxDate || curDate.getTime() <= maxDate.getTime()); }, 522 date: function(inst) { 523 return inst.selectedDates[0] || plugin.today(); }, 524 action: function(inst) { 525 var curDate = inst.selectedDates[0] || plugin.today(); 526 plugin.showMonth(this, curDate.getFullYear(), curDate.getMonth() + 1); } 527 }, 528 today: {text: 'todayText', status: 'todayStatus', // Today's month 529 keystroke: {keyCode: 36, ctrlKey: true}, // Ctrl + Home 530 enabled: function(inst) { 531 var minDate = inst.curMinDate(); 532 var maxDate = inst.get('maxDate'); 533 return (!minDate || plugin.today().getTime() >= minDate.getTime()) && 534 (!maxDate || plugin.today().getTime() <= maxDate.getTime()); }, 535 date: function(inst) { return plugin.today(); }, 536 action: function(inst) { plugin.showMonth(this); } 537 }, 538 clear: {text: 'clearText', status: 'clearStatus', // Clear the datepicker 539 keystroke: {keyCode: 35, ctrlKey: true}, // Ctrl + End 540 enabled: function(inst) { return true; }, 541 date: function(inst) { return null; }, 542 action: function(inst) { plugin.clear(this); } 543 }, 544 close: {text: 'closeText', status: 'closeStatus', // Close the datepicker 545 keystroke: {keyCode: 27}, // Escape 546 enabled: function(inst) { return true; }, 547 date: function(inst) { return null; }, 548 action: function(inst) { plugin.hide(this); } 549 }, 550 prevWeek: {text: 'prevWeekText', status: 'prevWeekStatus', // Previous week 551 keystroke: {keyCode: 38, ctrlKey: true}, // Ctrl + Up 552 enabled: function(inst) { 553 var minDate = inst.curMinDate();
554 return (!minDate || plugin.add(plugin.newDate(inst.drawDate), -7, 'd'). 555 getTime() >= minDate.getTime()); }, 556 date: function(inst) { return plugin.add(plugin.newDate(inst.drawDate), -7, 'd'); }, 557 action: function(inst) { plugin.changeDay(this, -7); } 558 }, 559 prevDay: {text: 'prevDayText', status: 'prevDayStatus', // Previous day 560 keystroke: {keyCode: 37, ctrlKey: true}, // Ctrl + Left 561 enabled: function(inst) { 562 var minDate = inst.curMinDate(); 563 return (!minDate || plugin.add(plugin.newDate(inst.drawDate), -1, 'd'). 564 getTime() >= minDate.getTime()); }, 565 date: function(inst) { return plugin.add(plugin.newDate(inst.drawDate), -1, 'd'); }, 566 action: function(inst) { plugin.changeDay(this, -1); } 567 }, 568 nextDay: {text: 'nextDayText', status: 'nextDayStatus', // Next day 569 keystroke: {keyCode: 39, ctrlKey: true}, // Ctrl + Right 570 enabled: function(inst) { 571 var maxDate = inst.get('maxDate'); 572 return (!maxDate || plugin.add(plugin.newDate(inst.drawDate), 1, 'd'). 573 getTime() <= maxDate.getTime()); }, 574 date: function(inst) { return plugin.add(plugin.newDate(inst.drawDate), 1, 'd'); }, 575 action: function(inst) { plugin.changeDay(this, 1); } 576 }, 577 nextWeek: {text: 'nextWeekText', status: 'nextWeekStatus', // Next week 578 keystroke: {keyCode: 40, ctrlKey: true}, // Ctrl + Down 579 enabled: function(inst) { 580 var maxDate = inst.get('maxDate'); 581 return (!maxDate || plugin.add(plugin.newDate(inst.drawDate), 7, 'd'). 582 getTime() <= maxDate.getTime()); }, 583 date: function(inst) { return plugin.add(plugin.newDate(inst.drawDate), 7, 'd'); }, 584 action: function(inst) { plugin.changeDay(this, 7); } 585 } 586 }, 587 588 /** Determine whether a command is enabled. 589 @callback DatepickCommandEnabled 590 @param inst {object} The current instance settings. 591 @return {boolean} <code>true</code> if this command is enabled, <code>false</code> if not. 592 @example enabled: function(inst) { 593 return !!inst.curMinDate(); 594 } */ 595 596 /** Calculate the representative date for a command. 597 @callback DatepickCommandDate 598 @param inst {object} The current instance settings. 599 @return {Date} A date appropriate for this command. 600 @example date: function(inst) { 601 return inst.curMinDate(); 602 } */ 603 604 /** Perform the action for a command. 605 @callback DatepickCommandAction 606 @param inst {object} The current instance settings. 607 @example date: function(inst) { 608 $.datepick.setDate(inst.elem, inst.curMinDate()); 609 } */ 610 611 /** Calculate the week of the year for a date. 612 @callback DatepickCalculateWeek 613 @param date {Date} The date to evaluate. 614 @return {number} The week of the year. 615 @example calculateWeek: function(date) { 616 return Math.floor(($.datepick.dayOfYear(date) - 1) / 7) + 1; 617 } */ 618 619 /** Provide information about an individual date shown in the calendar. 620 @callback DatepickOnDate 621 @param date {Date} The date to evaluate. 622 @return {object} Information about that date, with the properties above. 623 @property selectable {boolean} <code>true</code> if this date can be selected. 624 @property dateClass {string} Class(es) to be applied to the date. 625 @property content {string} The date cell content. 626 @property tooltip {string} A popup tooltip for the date. 627 @example onDate: function(date) { 628 return {selectable: date.getDay() > 0 && date.getDay() < 5, 629 dateClass: date.getDay() == 4 ? 'last-day' : ''}; 630 } */ 631 632 /** Update the datepicker display. 633 @callback DatepickOnShow 634 @param picker {jQuery} The datepicker <code>div</code> to be shown. 635 @param inst {object} The current instance settings. 636 @example onShow: function(picker, inst) { 637 picker.append('<button type="button">Hi</button>'). 638 find('button:last').click(function() { 639 alert('Hi!'); 640 }); 641 } */ 642 643 /** React to navigating through the months/years. 644 @callback DatepickOnChangeMonthYear 645 @param year {number} The new year. 646 @param month {number} The new month (1 to 12). 647 @example onChangeMonthYear: function(year, month) { 648 alert('Now in ' + month + '/' + year); 649 } */ 650 651 /** Datepicker on select callback. 652 Triggered when a date is selected. 653 @callback DatepickOnSelect 654 @param dates {Date[]} The selected date(s). 655 @example onSelect: function(dates) { 656 alert('Selected ' + dates); 657 } */ 658 659 /** Datepicker on close callback. 660 Triggered when a popup calendar is closed. 661 @callback DatepickOnClose 662 @param dates {Date[]} The selected date(s). 663 @example onClose: function(dates) { 664 alert('Selected ' + dates); 665 } */ 666 667 /** Default settings for the plugin. 668 @property [pickerClass=''] {string} CSS class to add to this instance of the datepicker. 669 @property [showOnFocus=true] {boolean} <code>true</code> for popup on focus, <code>false</code> for not.
670 @property [showTrigger=null] {string|Element|jQuery} Element to be cloned for a trigger, <code>null</code> for none. 671 @property [showAnim='show'] {string} Name of jQuery animation for popup, '' for no animation. 672 @property [showOptions=null] {object} Options for enhanced animations. 673 @property [showSpeed='normal'] {string} Duration of display/closure. 674 @property [popupContainer=null] {string|Element|jQuery} The element to which a popup calendar is added, <code>null</code> for body. 675 @property [alignment='bottom'] {string} Alignment of popup - with nominated corner of input: 676 'top' or 'bottom' aligns depending on language direction, 677 'topLeft', 'topRight', 'bottomLeft', 'bottomRight'. 678 @property [fixedWeeks=false] {boolean} <code>true</code> to always show 6 weeks, <code>false</code> to only show as many as are needed. 679 @property [firstDay=0] {number} First day of the week, 0 = Sunday, 1 = Monday, etc. 680 @property [calculateWeek=this.iso8601Week] {DatepickCalculateWeek} Calculate week of the year from a date, <code>null</code> for ISO8601. 681 @property [monthsToShow=1] {number|number[]} How many months to show, cols or [rows, cols]. 682 @property [monthsOffset=0] {number} How many months to offset the primary month by; 683 may be a function that takes the date and returns the offset. 684 @property [monthsToStep=1] {number} How many months to move when prev/next clicked. 685 @property [monthsToJump=12] {number} How many months to move when large prev/next clicked. 686 @property [useMouseWheel=true] {boolean} <code>true</code> to use mousewheel if available, <code>false</code> to never use it. 687 @property [changeMonth=true] {boolean} <code>true</code> to change month/year via drop-down, <code>false</code> for navigation only. 688 @property [yearRange='c-10:c+10'] {string} Range of years to show in drop-down: 'any' for direct text entry 689 or 'start:end', where start/end are '+-nn' for relative to today 690 or 'c+-nn' for relative to the currently selected date 691 or 'nnnn' for an absolute year. 692 @property [shortYearCutoff='+10'] {string} Cutoff for two-digit year in the current century. 693 @property [showOtherMonths=false] {boolean} <code>true</code> to show dates from other months, <code>false</code> to not show them. 694 @property [selectOtherMonths=false] {boolean} <code>true</code> to allow selection of dates from other months too. 695 @property [defaultDate=null] {string|number|Date} Date to show if no other selected. 696 @property [selectDefaultDate=false] {boolean} <code>true</code> to pre-select the default date if no other is chosen. 697 @property [minDate=null] {string|number|Date} The minimum selectable date. 698 @property [maxDate=null] {string|number|Date} The maximum selectable date. 699 @property [dateFormat='mm/dd/yyyy'] {string} Format for dates. 700 @property [autoSize=false] {boolean} <code>true</code> to size the input field according to the date format. 701 @property [rangeSelect=false] {boolean} Allows for selecting a date range on one date picker. 702 @property [rangeSeparator=' - '] {string} Text between two dates in a range. 703 @property [multiSelect=0] {number} Maximum number of selectable dates, zero for single select. 704 @property [multiSeparator=','] {string} Text between multiple dates. 705 @property [onDate=null] {DatepickOnDate} Callback as a date is added to the datepicker. 706 @property [onShow=null] {DatepickOnShow} Callback just before a datepicker is shown. 707 @property [onChangeMonthYear=null] {DatepickOnChangeMonthYear} Callback when a new month/year is selected. 708 @property [onSelect=null] {DatepickOnSelect} Callback when a date is selected. 709 @property [onClose=null] {DatepickOnClose} Callback when a datepicker is closed. 710 @property [altField=null] {string|Element|jQuery} Alternate field to update in synch with the datepicker. 711 @property [altFormat=null] {string} Date format for alternate field, defaults to <code>dateFormat</code>. 712 @property [constrainInput=true] {boolean} <code>true</code> to constrain typed input to <code>dateFormat</code> allowed characters. 713 @property [commandsAsDateFormat=false] {boolean} <code>true</code> to apply 714 <code><a href="#formatDate">formatDate</a></code> to the command texts. 715 @property [commands=this.commands] {object} Command actions that may be added to a layout by name. */ 716 defaultOptions: { 717 pickerClass: '', 718 showOnFocus: true, 719 showTrigger: null,
720 showAnim: 'show', 721 showOptions: {}, 722 showSpeed: 'normal', 723 popupContainer: null, 724 alignment: 'bottom', 725 fixedWeeks: false, 726 firstDay: 0, 727 calculateWeek: null, // this.iso8601Week, 728 monthsToShow: 1, 729 monthsOffset: 0, 730 monthsToStep: 1, 731 monthsToJump: 12, 732 useMouseWheel: true, 733 changeMonth: true, 734 yearRange: 'c-10:c+10', 735 shortYearCutoff: '+10', 736 showOtherMonths: false, 737 selectOtherMonths: false, 738 defaultDate: null, 739 selectDefaultDate: false, 740 minDate: null, 741 maxDate: null, 742 dateFormat: 'mm/dd/yyyy', 743 autoSize: false, 744 rangeSelect: false, 745 rangeSeparator: ' - ', 746 multiSelect: 0, 747 multiSeparator: ',', 748 onDate: null, 749 onShow: null, 750 onChangeMonthYear: null, 751 onSelect: null, 752 onClose: null, 753 altField: null, 754 altFormat: null, 755 constrainInput: true, 756 commandsAsDateFormat: false, 757 commands: {} // this.commands 758 }, 759 760 /** Localisations for the plugin. 761 Entries are objects indexed by the language code ('' being the default US/English). 762 Each object has the following attributes. 763 @property [monthNames=['January','February','March','April','May','June','July','August','September','October','November','December']] 764 The long names of the months. 765 @property [monthNamesShort=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']] 766 The short names of the months. 767 @property [dayNames=['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']] 768 The long names of the days of the week. 769 @property [dayNamesShort=['Sun','Mon','Tue','Wed','Thu','Fri','Sat']] The short names of the days of the week. 770 @property [dayNamesMin=['Su','Mo','Tu','We','Th','Fr','Sa']] The minimal names of the days of the week. 771 @property [dateFormat='mm/dd/yyyy'] {string} See options on <code><a href="#formatDate">formatDate</a></code>. 772 @property [firstDay=0] {number} The first day of the week, Sun = 0, Mon = 1, etc. 773 @property [renderer=this.defaultRenderer] {string} The rendering templates. 774 @property [prevText='<Prev'] {string} Text for the previous month command. 775 @property [prevStatus='Show the previous month'] {string} Status text for the previous month command. 776 @property [prevJumpText='<<'] {string} Text for the previous year command. 777 @property [prevJumpStatus='Show the previous year'] {string} Status text for the previous year command. 778 @property [nextText='Next>'] {string} Text for the next month command. 779 @property [nextStatus='Show the next month'] {string} Status text for the next month command. 780 @property [nextJumpText='>>'] {string} Text for the next year command. 781 @property [nextJumpStatus='Show the next year'] {string} Status text for the next year command. 782 @property [currentText='Current'] {string} Text for the current month command. 783 @property [currentStatus='Show the current month'] {string} Status text for the current month command. 784 @property [todayText='Today'] {string} Text for the today's month command. 785 @property [todayStatus='Show today\'s month'] {string} Status text for the today's month command. 786 @property [clearText='Clear'] {string} Text for the clear command. 787 @property [clearStatus='Clear all the dates'] {string} Status text for the clear command. 788 @property [closeText='Close'] {string} Text for the close command. 789 @property [closeStatus='Close the datepicker'] {string} Status text for the close command. 790 @property [yearStatus='Change the year'] {string} Status text for year selection. 791 @property [monthStatus='Change the month'] {string} Status text for month selection. 792 @property [weekText='Wk'] {string} Text for week of the year column header. 793 @property [weekStatus='Week of the year'] {string} Status text for week of the year column header. 794 @property [dayStatus='Select DD, M d, yyyy'] {string} Status text for selectable days. 795 @property [defaultStatus='Select a date'] {string} Status text shown by default. 796 @property [isRTL=false] {boolean} <code>true</code> if language is right-to-left. */ 797 regionalOptions: { // Available regional settings, indexed by language/country code 798 '': { // Default regional settings - English/US 799 monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 800 'July', 'August', 'September', 'October', 'November', 'December'], 801 monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], 802 dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], 803 dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], 804 dayNamesMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], 805 dateFormat: 'mm/dd/yyyy', 806 firstDay: 0, 807 renderer: {}, // this.defaultRenderer 808 prevText: '<Prev', 809 prevStatus: 'Show the previous month', 810 prevJumpText: '<<', 811 prevJumpStatus: 'Show the previous year', 812 nextText: 'Next>', 813 nextStatus: 'Show the next month', 814 nextJumpText: '>>', 815 nextJumpStatus: 'Show the next year', 816 currentText: 'Current', 817 currentStatus: 'Show the current month', 818 todayText: 'Today', 819 todayStatus: 'Show today\'s month', 820 clearText: 'Clear', 821 clearStatus: 'Clear all the dates', 822 closeText: 'Close', 823 closeStatus: 'Close the datepicker', 824 yearStatus: 'Change the year', 825 monthStatus: 'Change the month', 826 weekText: 'Wk', 827 weekStatus: 'Week of the year', 828 dayStatus: 'Free', 829 defaultStatus: 'Select a date', 830 isRTL: false 831 } 832 }, 833 834 /** Names of getter methods - those that can't be chained. */ 835 _getters: ['getDate', 'isDisabled', 'isSelectable', 'retrieveDate'],
836 837 _disabled: [], 838 839 _popupClass: pluginName + '-popup', // Marker for popup division 840 _triggerClass: pluginName + '-trigger', // Marker for trigger element 841 _disableClass: pluginName + '-disable', // Marker for disabled element 842 _monthYearClass: pluginName + '-month-year', // Marker for month/year inputs 843 _curMonthClass: pluginName + '-month-', // Marker for current month/year 844 _anyYearClass: pluginName + '-any-year', // Marker for year direct input 845 _curDoWClass: pluginName + '-dow-', // Marker for day of week 846 847 _ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) + 848 Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000), 849 _msPerDay: 24 * 60 * 60 * 1000, 850 851 /** The date format for use with Atom (RFC 3339/ISO 8601). */ 852 ATOM: 'yyyy-mm-dd', 853 /** The date format for use with cookies. */ 854 COOKIE: 'D, dd M yyyy', 855 /** The date format for full display. */ 856 FULL: 'DD, MM d, yyyy', 857 /** The date format for use with ISO 8601. */ 858 ISO_8601: 'yyyy-mm-dd', 859 /** The date format for Julian dates. */ 860 JULIAN: 'J', 861 /** The date format for use with RFC 822. */ 862 RFC_822: 'D, d M yy', 863 /** The date format for use with RFC 850. */ 864 RFC_850: 'DD, dd-M-yy', 865 /** The date format for use with RFC 1036. */ 866 RFC_1036: 'D, d M yy', 867 /** The date format for use with RFC 1123. */ 868 RFC_1123: 'D, d M yyyy', 869 /** The date format for use with RFC 2822. */ 870 RFC_2822: 'D, d M yyyy', 871 /** The date format for use with RSS (RFC 822). */ 872 RSS: 'D, d M yy', 873 /** The date format for Windows ticks. */ 874 TICKS: '!', 875 /** The date format for Unix timestamp. */ 876 TIMESTAMP: '@', 877 /** The date format for use with W3C (ISO 8601). */ 878 W3C: 'yyyy-mm-dd', 879 880 /** Format a date object into a string value. 881 The format can be combinations of the following: 882 <ul> 883 <li>d - day of month (no leading zero)</li> 884 <li>dd - day of month (two digit)</li> 885 <li>o - day of year (no leading zeros)</li> 886 <li>oo - day of year (three digit)</li> 887 <li>D - day name short</li> 888 <li>DD - day name long</li> 889 <li>w - week of year (no leading zero)</li> 890 <li>ww - week of year (two digit)</li> 891 <li>m - month of year (no leading zero)</li> 892 <li>mm - month of year (two digit)</li> 893 <li>M - month name short</li> 894 <li>MM - month name long</li> 895 <li>yy - year (two digit)</li> 896 <li>yyyy - year (four digit)</li> 897 <li>@ - Unix timestamp (s since 01/01/1970)</li> 898 <li>! - Windows ticks (100ns since 01/01/0001)</li> 899 <li>'...' - literal text</li> 900 <li>'' - single quote</li> 901 </ul> 902 @param [format=dateFormat] {string} The desired format of the date. 903 @param date {Date} The date value to format. 904 @param [settings] {object} With the properties shown below. 905 @property [dayNamesShort] {string[]} Abbreviated names of the days from Sunday. 906 @property [dayNames] {string[]} Names of the days from Sunday. 907 @property [monthNamesShort] {string[]} Abbreviated names of the months. 908 @property [monthNames] {string[]} Names of the months. 909 @property [calculateWeek] {DatepickCalculateWeek} Function that determines week of the year. 910 @return {string} The date in the above format. 911 @example var display = $.datepick.formatDate('yyyy-mm-dd', new Date(2014, 12-1, 25)) */ 912 formatDate: function(format, date, settings) { 913 if (typeof format !== 'string') { 914 settings = date; 915 date = format; 916 format = ''; 917 } 918 if (!date) { 919 return ''; 920 } 921 format = format || this.defaultOptions.dateFormat; 922 settings = settings || {}; 923 var dayNamesShort = settings.dayNamesShort || this.defaultOptions.dayNamesShort; 924 var dayNames = settings.dayNames || this.defaultOptions.dayNames; 925 var monthNamesShort = settings.monthNamesShort || this.defaultOptions.monthNamesShort; 926 var monthNames = settings.monthNames || this.defaultOptions.monthNames; 927 var calculateWeek = settings.calculateWeek || this.defaultOptions.calculateWeek; 928 // Check whether a format character is doubled 929 var doubled = function(match, step) { 930 var matches = 1; 931 while (iFormat + matches < format.length && format.charAt(iFormat + matches) === match) { 932 matches++; 933 } 934 iFormat += matches - 1; 935 return Math.floor(matches / (step || 1)) > 1; 936 }; 937 // Format a number, with leading zeroes if necessary 938 var formatNumber = function(match, value, len, step) { 939 var num = '' + value; 940 if (doubled(match, step)) { 941 while (num.length < len) { 942 num = '0' + num; 943 } 944 } 945 return num; 946 }; 947 // Format a name, short or long as requested
948 var formatName = function(match, value, shortNames, longNames) { 949 return (doubled(match) ? longNames[value] : shortNames[value]); 950 }; 951 var output = ''; 952 var literal = false; 953 for (var iFormat = 0; iFormat < format.length; iFormat++) { 954 if (literal) { 955 if (format.charAt(iFormat) === "'" && !doubled("'")) { 956 literal = false; 957 } 958 else { 959 output += format.charAt(iFormat); 960 } 961 } 962 else { 963 switch (format.charAt(iFormat)) { 964 case 'd': output += formatNumber('d', date.getDate(), 2); break; 965 case 'D': output += formatName('D', date.getDay(), 966 dayNamesShort, dayNames); break; 967 case 'o': output += formatNumber('o', this.dayOfYear(date), 3); break; 968 case 'w': output += formatNumber('w', calculateWeek(date), 2); break; 969 case 'm': output += formatNumber('m', date.getMonth() + 1, 2); break; 970 case 'M': output += formatName('M', date.getMonth(), 971 monthNamesShort, monthNames); break; 972 case 'y': 973 output += (doubled('y', 2) ? date.getFullYear() : 974 (date.getFullYear() % 100 < 10 ? '0' : '') + date.getFullYear() % 100); 975 break; 976 case '@': output += Math.floor(date.getTime() / 1000); break; 977 case '!': output += date.getTime() * 10000 + this._ticksTo1970; break; 978 case "'": 979 if (doubled("'")) { 980 output += "'"; 981 } 982 else { 983 literal = true; 984 } 985 break; 986 default: 987 output += format.charAt(iFormat); 988 } 989 } 990 } 991 return output; 992 }, 993 994 /** Parse a string value into a date object. 995 See <code><a href="#formatDate">formatDate</a></code> for the possible formats, plus: 996 <ul> 997 <li>* - ignore rest of string</li> 998 </ul> 999 @param format {string} The expected format of the date ('' for default datepicker format). 1000 @param value {string} The date in the above format. 1001 @param [settings] {object} With the properties shown above. 1002 @property [shortYearCutoff] {number} the cutoff year for determining the century. 1003 @property [dayNamesShort] {string[]} abbreviated names of the days from Sunday. 1004 @property [dayNames] {string[]} names of the days from Sunday. 1005 @property [monthNamesShort] {string[]} abbreviated names of the months. 1006 @property [monthNames] {string[]} names of the months. 1007 @return {Date} The extracted date value or <code>null</code> if value is blank. 1008 @throws Errors if the format and/or value are missing, if the value doesn't match the format, 1009 or if the date is invalid. 1010 @example var date = $.datepick.parseDate('dd/mm/yyyy', '25/12/2014') */ 1011 parseDate: function(format, value, settings) { 1012 if (value == null) { 1013 throw 'Invalid arguments'; 1014 } 1015 value = (typeof value === 'object' ? value.toString() : value + ''); 1016 if (value === '') { 1017 return null; 1018 } 1019 format = format || this.defaultOptions.dateFormat; 1020 settings = settings || {}; 1021 var shortYearCutoff = settings.shortYearCutoff || this.defaultOptions.shortYearCutoff; 1022 shortYearCutoff = (typeof shortYearCutoff !== 'string' ? shortYearCutoff : 1023 this.today().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); 1024 var dayNamesShort = settings.dayNamesShort || this.defaultOptions.dayNamesShort; 1025 var dayNames = settings.dayNames || this.defaultOptions.dayNames; 1026 var monthNamesShort = settings.monthNamesShort || this.defaultOptions.monthNamesShort; 1027 var monthNames = settings.monthNames || this.defaultOptions.monthNames; 1028 var year = -1; 1029 var month = -1; 1030 var day = -1; 1031 var doy = -1; 1032 var shortYear = false; 1033 var literal = false; 1034 // Check whether a format character is doubled 1035 var doubled = function(match, step) { 1036 var matches = 1; 1037 while (iFormat + matches < format.length && format.charAt(iFormat + matches) === match) { 1038 matches++; 1039 } 1040 iFormat += matches - 1; 1041 return Math.floor(matches / (step || 1)) > 1; 1042 }; 1043 // Extract a number from the string value 1044 var getNumber = function(match, step) { 1045 var isDoubled = doubled(match, step); 1046 var size = [2, 3, isDoubled ? 4 : 2, 11, 20]['oy@!'.indexOf(match) + 1]; 1047 var digits = new RegExp('^-?\\d{1,' + size + '}'); 1048 var num = value.substring(iValue).match(digits); 1049 if (!num) { 1050 throw 'Missing number at position {0}'.replace(/\{0\}/, iValue); 1051 } 1052 iValue += num[0].length; 1053 return parseInt(num[0], 10); 1054 }; 1055 // Extract a name from the string value and convert to an index 1056 var getName = function(match, shortNames, longNames, step) { 1057 var names = (doubled(match, step) ? longNames : shortNames); 1058 for (var i = 0; i < names.length; i++) { 1059 if (value.substr(iValue, names[i].length).toLowerCase() === names[i].toLowerCase()) { 1060 iValue += names[i].length; 1061 return i + 1; 1062 } 1063 } 1064 throw 'Unknown name at position {0}'.replace(/\{0\}/, iValue); 1065 }; 1066 // Confirm that a literal character matches the string value 1067 var checkLiteral = function() { 1068 if (value.charAt(iValue) !== format.charAt(iFormat)) { 1069 throw 'Unexpected literal at position {0}'.replace(/\{0\}/, iValue); 1070 } 1071 iValue++; 1072 }; 1073 var iValue = 0; 1074 for (var iFormat = 0; iFormat < format.length; iFormat++) { 1075 if (literal) { 1076 if (format.charAt(iFormat) === "'" && !doubled("'")) { 1077 literal = false;
1078 } 1079 else { 1080 checkLiteral(); 1081 } 1082 } 1083 else { 1084 switch (format.charAt(iFormat)) { 1085 case 'd': day = getNumber('d'); break; 1086 case 'D': getName('D', dayNamesShort, dayNames); break; 1087 case 'o': doy = getNumber('o'); break; 1088 case 'w': getNumber('w'); break; 1089 case 'm': month = getNumber('m'); break; 1090 case 'M': month = getName('M', monthNamesShort, monthNames); break; 1091 case 'y': 1092 var iSave = iFormat; 1093 shortYear = !doubled('y', 2); 1094 iFormat = iSave; 1095 year = getNumber('y', 2); 1096 break; 1097 case '@': 1098 var date = this._normaliseDate(new Date(getNumber('@') * 1000)); 1099 year = date.getFullYear(); 1100 month = date.getMonth() + 1; 1101 day = date.getDate(); 1102 break; 1103 case '!': 1104 var date = this._normaliseDate( 1105 new Date((getNumber('!') - this._ticksTo1970) / 10000)); 1106 year = date.getFullYear(); 1107 month = date.getMonth() + 1; 1108 day = date.getDate(); 1109 break; 1110 case '*': iValue = value.length; break; 1111 case "'": 1112 if (doubled("'")) { 1113 checkLiteral(); 1114 } 1115 else { 1116 literal = true; 1117 } 1118 break; 1119 default: checkLiteral(); 1120 } 1121 } 1122 } 1123 if (iValue < value.length) { 1124 throw 'Additional text found at end'; 1125 } 1126 if (year === -1) { 1127 year = this.today().getFullYear(); 1128 } 1129 else if (year < 100 && shortYear) { 1130 year += (shortYearCutoff === -1 ? 1900 : this.today().getFullYear() - 1131 this.today().getFullYear() % 100 - (year <= shortYearCutoff ? 0 : 100)); 1132 } 1133 if (doy > -1) { 1134 month = 1; 1135 day = doy; 1136 for (var dim = this.daysInMonth(year, month); day > dim; 1137 dim = this.daysInMonth(year, month)) { 1138 month++; 1139 day -= dim; 1140 } 1141 } 1142 var date = this.newDate(year, month, day); 1143 if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) { 1144 throw 'Invalid date'; 1145 } 1146 return date; 1147 }, 1148 1149 /** A date may be specified as an exact value or a relative one. 1150 @param dateSpec {Date|number|string} The date as an object or string 1151 in the given format or an offset - numeric days from today, 1152 or string amounts and periods, e.g. '+1m +2w'. 1153 @param defaultDate {Date} The date to use if no other supplied, may be <code>null</code>. 1154 @param [currentDate] {Date} The current date as a possible basis for relative dates, 1155 if <code>null</code> today is used. 1156 @param dateFormat {string} The expected date format - see <code><a href="#formatDate">formatDate</a></code>. 1157 @param settings {object} With the properties shown above. 1158 @property [shortYearCutoff] {number} The cutoff year for determining the century. 1159 @property [dayNamesShort] {string[]} Abbreviated names of the days from Sunday. 1160 @property [dayNames] {string[]} Names of the days from Sunday. 1161 @property [monthNamesShort] {string[]} Abbreviated names of the months. 1162 @property [monthNames] {string[]} Names of the months. 1163 @return {Date} The decoded date. 1164 @example $.datepick.determineDate('+1m +2w', new Date()) */ 1165 determineDate: function(dateSpec, defaultDate, currentDate, dateFormat, settings) { 1166 if (currentDate && typeof currentDate !== 'object') { 1167 settings = dateFormat; 1168 dateFormat = currentDate; 1169 currentDate = null; 1170 } 1171 if (typeof dateFormat !== 'string') { 1172 settings = dateFormat; 1173 dateFormat = ''; 1174 } 1175 var offsetString = function(offset) { 1176 try { 1177 return plugin.parseDate(dateFormat, offset, settings); 1178 } 1179 catch (e) { 1180 // Ignore 1181 } 1182 offset = offset.toLowerCase(); 1183 var date = (offset.match(/^c/) && currentDate ? plugin.newDate(currentDate) : null) || 1184 plugin.today(); 1185 var pattern = /([+-]?[0-9]+)\s*(d|w|m|y)?/g; 1186 var matches = null; 1187 while (matches = pattern.exec(offset)) { 1188 date = plugin.add(date, parseInt(matches[1], 10), matches[2] || 'd'); 1189 } 1190 return date; 1191 }; 1192 defaultDate = (defaultDate ? plugin.newDate(defaultDate) : null); 1193 dateSpec = (dateSpec == null ? defaultDate : 1194 (typeof dateSpec === 'string' ? offsetString(dateSpec) : (typeof dateSpec === 'number' ? 1195 (isNaN(dateSpec) || dateSpec === Infinity || dateSpec === -Infinity ? defaultDate :
1196 plugin.add(plugin.today(), dateSpec, 'd')) : plugin.newDate(dateSpec)))); 1197 return dateSpec; 1198 }, 1199 1200 /** Find the number of days in a given month. 1201 @param year {Date|number} The date to get days for or the full year. 1202 @param month {number} The month (1 to 12). 1203 @return {number} The number of days in this month. 1204 @example var days = $.datepick.daysInMonth(2014, 12) */ 1205 daysInMonth: function(year, month) { 1206 month = (year.getFullYear ? year.getMonth() + 1 : month); 1207 year = (year.getFullYear ? year.getFullYear() : year); 1208 return this.newDate(year, month + 1, 0).getDate(); 1209 }, 1210 1211 /** Calculate the day of the year for a date. 1212 @param year {Date|number} The date to get the day-of-year for or the full year. 1213 @param month {number} The month (1-12). 1214 @param day {number} The day. 1215 @return {number} The day of the year. 1216 @example var doy = $.datepick.dayOfYear(2014, 12, 25) */ 1217 dayOfYear: function(year, month, day) { 1218 var date = (year.getFullYear ? year : plugin.newDate(year, month, day)); 1219 var newYear = plugin.newDate(date.getFullYear(), 1, 1); 1220 return Math.floor((date.getTime() - newYear.getTime()) / plugin._msPerDay) + 1; 1221 }, 1222 1223 /** Set as <code>calculateWeek</code> to determine the week of the year based on the ISO 8601 definition. 1224 @param year {Date|number} The date to get the week for or the full year. 1225 @param month {number} The month (1-12). 1226 @param day {number} The day. 1227 @return {number} The number of the week within the year that contains this date. 1228 @example var week = $.datepick.iso8601Week(2014, 12, 25) */ 1229 iso8601Week: function(year, month, day) { 1230 var checkDate = (year.getFullYear ? 1231 new Date(year.getTime()) : plugin.newDate(year, month, day)); 1232 // Find Thursday of this week starting on Monday 1233 checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); 1234 var time = checkDate.getTime(); 1235 checkDate.setMonth(0, 1); // Compare with Jan 1 1236 return Math.floor(Math.round((time - checkDate) / plugin._msPerDay) / 7) + 1; 1237 }, 1238 1239 /** Return today's date. 1240 @return {Date} Today. 1241 @example $.datepick.today() */ 1242 today: function() { 1243 return this._normaliseDate(new Date()); 1244 }, 1245 1246 /** Return a new date. 1247 @param year {Date|number} The date to clone or the year. 1248 @param month {number} The month (1-12). 1249 @param day {number} The day. 1250 @return {Date} The date. 1251 @example $.datepick.newDate(oldDate) 1252 $.datepick.newDate(2014, 12, 25) */ 1253 newDate: function(year, month, day) { 1254 return (!year ? null : (year.getFullYear ? this._normaliseDate(new Date(year.getTime())) : 1255 new Date(year, month - 1, day, 12))); 1256 }, 1257 1258 /** Standardise a date into a common format - time portion is 12 noon. 1259 @private 1260 @param date {Date} The date to standardise. 1261 @return {Date} The normalised date. */ 1262 _normaliseDate: function(date) { 1263 if (date) { 1264 date.setHours(12, 0, 0, 0); 1265 } 1266 return date; 1267 }, 1268 1269 /** Set the year for a date. 1270 @param date {Date} The original date. 1271 @param year {number} The new year. 1272 @return {Date} The updated date. 1273 @example $.datepick.year(date, 2014) */ 1274 year: function(date, year) { 1275 date.setFullYear(year); 1276 return this._normaliseDate(date); 1277 }, 1278 1279 /** Set the month for a date. 1280 @param date {Date} The original date. 1281 @param month {number} The new month (1-12). 1282 @return {Date} The updated date. 1283 @example $.datepick.month(date, 12) */ 1284 month: function(date, month) { 1285 date.setMonth(month - 1); 1286 return this._normaliseDate(date); 1287 }, 1288 1289 /** Set the day for a date. 1290 @param date {Date} The original date. 1291 @param day {number} The new day of the month. 1292 @return {Date} The updated date. 1293 @example $.datepick.day(date, 25) */ 1294 day: function(date, day) { 1295 date.setDate(day); 1296 return this._normaliseDate(date); 1297 }, 1298 1299 /** Add a number of periods to a date. 1300 @param date {Date} The original date. 1301 @param amount {number} The number of periods. 1302 @param period {string} The type of period d/w/m/y. 1303 @return {Date} The updated date. 1304 @example $.datepick.add(date, 10, 'd') */
1305 add: function(date, amount, period) { 1306 if (period === 'd' || period === 'w') { 1307 this._normaliseDate(date); 1308 date.setDate(date.getDate() + amount * (period === 'w' ? 7 : 1)); 1309 } 1310 else { 1311 var year = date.getFullYear() + (period === 'y' ? amount : 0); 1312 var month = date.getMonth() + (period === 'm' ? amount : 0); 1313 date.setTime(plugin.newDate(year, month + 1, 1314 Math.min(date.getDate(), this.daysInMonth(year, month + 1))).getTime()); 1315 } 1316 return date; 1317 }, 1318 1319 /** Apply the months offset value to a date. 1320 @private 1321 @param date {Date} The original date. 1322 @param inst {object} The current instance settings. 1323 @return {Date} The updated date. */ 1324 _applyMonthsOffset: function(date, inst) { 1325 var monthsOffset = inst.options.monthsOffset; 1326 if ($.isFunction(monthsOffset)) { 1327 monthsOffset = monthsOffset.apply(inst.elem[0], [date]); 1328 } 1329 return plugin.add(date, -monthsOffset, 'm'); 1330 }, 1331 1332 _init: function() { 1333 this.defaultOptions.commands = this.commands; 1334 this.defaultOptions.calculateWeek = this.iso8601Week; 1335 this.regionalOptions[''].renderer = this.defaultRenderer; 1336 this._super(); 1337 }, 1338 1339 _instSettings: function(elem, options) { 1340 return {selectedDates: [], drawDate: null, pickingRange: false, 1341 inline: ($.inArray(elem[0].nodeName.toLowerCase(), ['div', 'span']) > -1), 1342 get: function(name) { // Get a setting value, computing if necessary 1343 if ($.inArray(name, ['defaultDate', 'minDate', 'maxDate']) > -1) { // Decode date settings 1344 return plugin.determineDate(this.options[name], null, 1345 this.selectedDates[0], this.options.dateFormat, this.getConfig()); 1346 } 1347 return this.options[name]; 1348 }, 1349 curMinDate: function() { 1350 return (this.pickingRange ? this.selectedDates[0] : this.get('minDate')); 1351 }, 1352 getConfig: function() { 1353 return {dayNamesShort: this.options.dayNamesShort, dayNames: this.options.dayNames, 1354 monthNamesShort: this.options.monthNamesShort, monthNames: this.options.monthNames, 1355 calculateWeek: this.options.calculateWeek, 1356 shortYearCutoff: this.options.shortYearCutoff}; 1357 } 1358 }; 1359 }, 1360 1361 _postAttach: function(elem, inst) { 1362 if (inst.inline) { 1363 inst.drawDate = plugin._checkMinMax(plugin.newDate(inst.selectedDates[0] || 1364 inst.get('defaultDate') || plugin.today()), inst); 1365 inst.prevDate = plugin.newDate(inst.drawDate); 1366 this._update(elem[0]); 1367 if ($.fn.mousewheel) { 1368 elem.mousewheel(this._doMouseWheel); 1369 } 1370 } 1371 else { 1372 this._attachments(elem, inst); 1373 elem.on('keydown.' + inst.name, this._keyDown).on('keypress.' + inst.name, this._keyPress). 1374 on('keyup.' + inst.name, this._keyUp); 1375 if (elem.attr('disabled')) { 1376 this.disable(elem[0]); 1377 } 1378 } 1379 }, 1380 1381 _optionsChanged: function(elem, inst, options) { 1382 if (options.calendar && options.calendar !== inst.options.calendar) { 1383 var discardDate = function(name) { 1384 return (typeof inst.options[name] === 'object' ? null : inst.options[name]); 1385 }; 1386 options = $.extend({defaultDate: discardDate('defaultDate'), 1387 minDate: discardDate('minDate'), maxDate: discardDate('maxDate')}, options); 1388 inst.selectedDates = []; 1389 inst.drawDate = null; 1390 } 1391 var dates = inst.selectedDates; 1392 $.extend(inst.options, options); 1393 this.setDate(elem[0], dates, null, false, true); 1394 inst.pickingRange = false; 1395 inst.drawDate = plugin.newDate(this._checkMinMax( 1396 (inst.options.defaultDate ? inst.get('defaultDate') : inst.drawDate) || 1397 inst.get('defaultDate') || plugin.today(), inst)); 1398 if (!inst.inline) { 1399 this._attachments(elem, inst); 1400 } 1401 if (inst.inline || inst.div) { 1402 this._update(elem[0]); 1403 } 1404 }, 1405 1406 /** Attach events and trigger, if necessary. 1407 @private 1408 @param elem {jQuery} The control to affect. 1409 @param inst {object} The current instance settings. */ 1410 _attachments: function(elem, inst) { 1411 elem.off('focus.' + inst.name); 1412 if (inst.options.showOnFocus) { 1413 elem.on('focus.' + inst.name, this.show); 1414 } 1415 if (inst.trigger) { 1416 inst.trigger.remove(); 1417 } 1418 var trigger = inst.options.showTrigger; 1419 inst.trigger = (!trigger ? $([]) : 1420 $(trigger).clone().removeAttr('id').addClass(this._triggerClass) 1421 [inst.options.isRTL ? 'insertBefore' : 'insertAfter'](elem). 1422 click(function() { 1423 if (!plugin.isDisabled(elem[0])) { 1424 plugin[plugin.curInst === inst ? 'hide' : 'show'](elem[0]); 1425 } 1426 })); 1427 this._autoSize(elem, inst); 1428 var dates = this._extractDates(inst, elem.val()); 1429 if (dates) { 1430 this.setDate(elem[0], dates, null, true); 1431 } 1432 var defaultDate = inst.get('defaultDate'); 1433 if (inst.options.selectDefaultDate && defaultDate && inst.selectedDates.length === 0) {
1434 this.setDate(elem[0], plugin.newDate(defaultDate || plugin.today())); 1435 } 1436 }, 1437 1438 /** Apply the maximum length for the date format. 1439 @private 1440 @param elem {jQuery} The control to affect. 1441 @param inst {object} The current instance settings. */ 1442 _autoSize: function(elem, inst) { 1443 if (inst.options.autoSize && !inst.inline) { 1444 var date = plugin.newDate(2009, 10, 20); // Ensure double digits 1445 var dateFormat = inst.options.dateFormat; 1446 if (dateFormat.match(/[DM]/)) { 1447 var findMax = function(names) { 1448 var max = 0; 1449 var maxI = 0; 1450 for (var i = 0; i < names.length; i++) { 1451 if (names[i].length > max) { 1452 max = names[i].length; 1453 maxI = i; 1454 } 1455 } 1456 return maxI; 1457 }; 1458 date.setMonth(findMax(inst.options[dateFormat.match(/MM/) ? // Longest month 1459 'monthNames' : 'monthNamesShort'])); 1460 date.setDate(findMax(inst.options[dateFormat.match(/DD/) ? // Longest day 1461 'dayNames' : 'dayNamesShort']) + 20 - date.getDay()); 1462 } 1463 inst.elem.attr('size', plugin.formatDate(dateFormat, date, inst.getConfig()).length); 1464 } 1465 }, 1466 1467 _preDestroy: function(elem, inst) { 1468 if (inst.trigger) { 1469 inst.trigger.remove(); 1470 } 1471 elem.empty().off('.' + inst.name); 1472 if (inst.inline && $.fn.mousewheel) { 1473 elem.unmousewheel(); 1474 } 1475 if (!inst.inline && inst.options.autoSize) { 1476 elem.removeAttr('size'); 1477 } 1478 }, 1479 1480 /** Apply multiple event functions. 1481 @param fns {function} The functions to apply. 1482 @example onShow: multipleEvents(fn1, fn2, ...) */ 1483 multipleEvents: function(fns) { 1484 var funcs = arguments; 1485 return function(args) { 1486 for (var i = 0; i < funcs.length; i++) { 1487 funcs[i].apply(this, arguments); 1488 } 1489 }; 1490 }, 1491 1492 /** Enable the control. 1493 @param elem {Element} The control to affect. 1494 @example $(selector).datepick('enable') */ 1495 enable: function(elem) { 1496 elem = $(elem); 1497 if (!elem.hasClass(this._getMarker())) { 1498 return; 1499 } 1500 var inst = this._getInst(elem); 1501 if (inst.inline) { 1502 elem.children('.' + this._disableClass).remove().end(). 1503 find('button,select').prop('disabled', false).end(). 1504 find('a').attr('href', 'javascript:void(0)'); 1505 } 1506 else { 1507 elem.prop('disabled', false); 1508 inst.trigger.filter('button.' + this._triggerClass).prop('disabled', false).end(). 1509 filter('img.' + this._triggerClass).css({opacity: '1.0', cursor: ''}); 1510 } 1511 this._disabled = $.map(this._disabled, 1512 function(value) { return (value === elem[0] ? null : value); }); // Delete entry 1513 }, 1514 1515 /** Disable the control. 1516 @param elem {Element} The control to affect. 1517 @example $(selector).datepick('disable') */ 1518 disable: function(elem) { 1519 elem = $(elem); 1520 if (!elem.hasClass(this._getMarker())) { 1521 return; 1522 } 1523 var inst = this._getInst(elem); 1524 if (inst.inline) { 1525 var inline = elem.children(':last'); 1526 var offset = inline.offset(); 1527 var relOffset = {left: 0, top: 0}; 1528 inline.parents().each(function() { 1529 if ($(this).css('position') === 'relative') { 1530 relOffset = $(this).offset(); 1531 return false; 1532 } 1533 }); 1534 var zIndex = elem.css('zIndex'); 1535 zIndex = (zIndex === 'auto' ? 0 : parseInt(zIndex, 10)) + 1; 1536 elem.prepend('<div class="' + this._disableClass + '" style="' + 1537 'width: ' + inline.outerWidth() + 'px; height: ' + inline.outerHeight() + 1538 'px; left: ' + (offset.left - relOffset.left) + 'px; top: ' + 1539 (offset.top - relOffset.top) + 'px; z-index: ' + zIndex + '"></div>'). 1540 find('button,select').prop('disabled', true).end(). 1541 find('a').removeAttr('href'); 1542 } 1543 else { 1544 elem.prop('disabled', true); 1545 inst.trigger.filter('button.' + this._triggerClass).prop('disabled', true).end(). 1546 filter('img.' + this._triggerClass).css({opacity: '0.5', cursor: 'default'}); 1547 } 1548 this._disabled = $.map(this._disabled, 1549 function(value) { return (value === elem[0] ? null : value); }); // Delete entry 1550 this._disabled.push(elem[0]); 1551 }, 1552 1553 /** Is the first field in a jQuery collection disabled as a datepicker? 1554 @param elem {Element} The control to examine. 1555 @return {boolean} <code>true</code> if disabled, <code>false</code> if enabled. 1556 @example if ($(selector).datepick('isDisabled')) {...} */ 1557 isDisabled: function(elem) { 1558 return (elem && $.inArray(elem, this._disabled) > -1); 1559 }, 1560 1561 /** Show a popup datepicker. 1562 @param elem {Event|Element} a focus event or the control to use. 1563 @example $(selector).datepick('show') */ 1564 show: function(elem) { 1565 elem = $(elem.target || elem); 1566 var inst = plugin._getInst(elem); 1567 if (plugin.curInst === inst) { 1568 return; 1569 } 1570 if (plugin.curInst) { 1571 plugin.hide(plugin.curInst, true); 1572 } 1573 if (!$.isEmptyObject(inst)) { 1574 // Retrieve existing date(s) 1575 inst.lastVal = null; 1576 inst.selectedDates = plugin._extractDates(inst, elem.val()); 1577 inst.pickingRange = false;
1578 inst.drawDate = plugin._checkMinMax(plugin.newDate(inst.selectedDates[0] || 1579 inst.get('defaultDate') || plugin.today()), inst); 1580 inst.prevDate = plugin.newDate(inst.drawDate); 1581 plugin.curInst = inst; 1582 // Generate content 1583 plugin._update(elem[0], true); 1584 // Adjust position before showing 1585 var offset = plugin._checkOffset(inst); 1586 inst.div.css({left: offset.left, top: offset.top}); 1587 // And display 1588 var showAnim = inst.options.showAnim; 1589 var showSpeed = inst.options.showSpeed; 1590 showSpeed = (showSpeed === 'normal' && $.ui && 1591 parseInt($.ui.version.substring(2)) >= 8 ? '_default' : showSpeed); 1592 if ($.effects && ($.effects[showAnim] || ($.effects.effect && $.effects.effect[showAnim]))) { 1593 var data = inst.div.data(); // Update old effects data 1594 for (var key in data) { 1595 if (key.match(/^ec\.storage\./)) { 1596 data[key] = inst._mainDiv.css(key.replace(/ec\.storage\./, '')); 1597 } 1598 } 1599 inst.div.data(data).show(showAnim, inst.options.showOptions, showSpeed); 1600 } 1601 else { 1602 inst.div[showAnim || 'show'](showAnim ? showSpeed : 0); 1603 } 1604 } 1605 }, 1606 1607 /** Extract possible dates from a string. 1608 @private 1609 @param inst {object} The current instance settings. 1610 @param text {string} The text to extract from. 1611 @return {Date[]} The extracted dates. */ 1612 _extractDates: function(inst, datesText) { 1613 if (datesText === inst.lastVal) { 1614 return; 1615 } 1616 inst.lastVal = datesText; 1617 datesText = datesText.split(inst.options.multiSelect ? inst.options.multiSeparator : 1618 (inst.options.rangeSelect ? inst.options.rangeSeparator : '\x00')); 1619 var dates = []; 1620 for (var i = 0; i < datesText.length; i++) { 1621 try { 1622 var date = plugin.parseDate(inst.options.dateFormat, datesText[i], inst.getConfig()); 1623 if (date) { 1624 var found = false; 1625 for (var j = 0; j < dates.length; j++) { 1626 if (dates[j].getTime() === date.getTime()) { 1627 found = true; 1628 break; 1629 } 1630 } 1631 if (!found) { 1632 dates.push(date); 1633 } 1634 } 1635 } 1636 catch (e) { 1637 // Ignore 1638 } 1639 } 1640 dates.splice(inst.options.multiSelect || (inst.options.rangeSelect ? 2 : 1), dates.length); 1641 if (inst.options.rangeSelect && dates.length === 1) { 1642 dates[1] = dates[0]; 1643 } 1644 return dates; 1645 }, 1646 1647 /** Update the datepicker display. 1648 @private 1649 @param elem {Event|Element} a focus event or the control to use. 1650 @param hidden {boolean} <code>true</code> to initially hide the datepicker. */ 1651 _update: function(elem, hidden) { 1652 elem = $(elem.target || elem); 1653 var inst = plugin._getInst(elem); 1654 if (!$.isEmptyObject(inst)) { 1655 if (inst.inline || plugin.curInst === inst) { 1656 if ($.isFunction(inst.options.onChangeMonthYear) && (!inst.prevDate || 1657 inst.prevDate.getFullYear() !== inst.drawDate.getFullYear() || 1658 inst.prevDate.getMonth() !== inst.drawDate.getMonth())) { 1659 inst.options.onChangeMonthYear.apply(elem[0], 1660 [inst.drawDate.getFullYear(), inst.drawDate.getMonth() + 1]); 1661 } 1662 } 1663 if (inst.inline) { 1664 elem.html(this._generateContent(elem[0], inst)); 1665 } 1666 else if (plugin.curInst === inst) { 1667 if (!inst.div) { 1668 inst.div = $('<div></div>').addClass(this._popupClass). 1669 css({display: (hidden ? 'none' : 'static'), position: 'absolute', 1670 left: elem.offset().left, top: elem.offset().top + elem.outerHeight()}). 1671 appendTo($(inst.options.popupContainer || 'body')); 1672 if ($.fn.mousewheel) { 1673 inst.div.mousewheel(this._doMouseWheel); 1674 } 1675 } 1676 inst.div.html(this._generateContent(elem[0], inst)); 1677 elem.focus(); 1678 } 1679 } 1680 }, 1681 1682 /** Update the input field and any alternate field with the current dates. 1683 @private 1684 @param elem {Element} The control to use. 1685 @param keyUp {boolean} <code>true</code> if coming from <code>keyUp</code> processing (internal). */ 1686 _updateInput: function(elem, keyUp) { 1687 var inst = this._getInst(elem); 1688 if (!$.isEmptyObject(inst)) { 1689 var value = ''; 1690 var altValue = ''; 1691 var sep = (inst.options.multiSelect ? inst.options.multiSeparator : 1692 inst.options.rangeSeparator); 1693 var altFormat = inst.options.altFormat || inst.options.dateFormat; 1694 for (var i = 0; i < inst.selectedDates.length; i++) { 1695 value += (keyUp ? '' : (i > 0 ? sep : '') + plugin.formatDate( 1696 inst.options.dateFormat, inst.selectedDates[i], inst.getConfig())); 1697 altValue += (i > 0 ? sep : '') + plugin.formatDate( 1698 altFormat, inst.selectedDates[i], inst.getConfig()); 1699 } 1700 if (!inst.inline && !keyUp) { 1701 $(elem).val(value); 1702 } 1703 $(inst.options.altField).val(altValue); 1704 if ($.isFunction(inst.options.onSelect) && !keyUp && !inst.inSelect) { 1705 inst.inSelect = true; // Prevent endless loops 1706 inst.options.onSelect.apply(elem, [inst.selectedDates]); 1707 inst.inSelect = false;
1708 } 1709 } 1710 }, 1711 1712 /** Retrieve the size of left and top borders for an element. 1713 @private 1714 @param elem {jQuery} The element of interest. 1715 @return {number[]} The left and top borders. */ 1716 _getBorders: function(elem) { 1717 var convert = function(value) { 1718 return {thin: 1, medium: 3, thick: 5}[value] || value; 1719 }; 1720 return [parseFloat(convert(elem.css('border-left-width'))), 1721 parseFloat(convert(elem.css('border-top-width')))]; 1722 }, 1723 1724 /** Check positioning to remain on the screen. 1725 @private 1726 @param inst {object} The current instance settings. 1727 @return {object} The updated offset for the datepicker. */ 1728 _checkOffset: function(inst) { 1729 var base = (inst.elem.is(':hidden') && inst.trigger ? inst.trigger : inst.elem); 1730 var offset = base.offset(); 1731 var browserWidth = $(window).width(); 1732 var browserHeight = $(window).height(); 1733 if (browserWidth === 0) { 1734 return offset; 1735 } 1736 var isFixed = false; 1737 $(inst.elem).parents().each(function() { 1738 isFixed |= $(this).css('position') === 'fixed'; 1739 return !isFixed; 1740 }); 1741 var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; 1742 var scrollY = document.documentElement.scrollTop || document.body.scrollTop; 1743 var above = offset.top - (isFixed ? scrollY : 0) - inst.div.outerHeight(); 1744 var below = offset.top - (isFixed ? scrollY : 0) + base.outerHeight(); 1745 var alignL = offset.left - (isFixed ? scrollX : 0); 1746 var alignR = offset.left - (isFixed ? scrollX : 0) + base.outerWidth() - inst.div.outerWidth(); 1747 var tooWide = (offset.left - scrollX + inst.div.outerWidth()) > browserWidth; 1748 var tooHigh = (offset.top - scrollY + inst.elem.outerHeight() + 1749 inst.div.outerHeight()) > browserHeight; 1750 inst.div.css('position', isFixed ? 'fixed' : 'absolute'); 1751 var alignment = inst.options.alignment; 1752 if (alignment === 'topLeft') { 1753 offset = {left: alignL, top: above}; 1754 } 1755 else if (alignment === 'topRight') { 1756 offset = {left: alignR, top: above}; 1757 } 1758 else if (alignment === 'bottomLeft') { 1759 offset = {left: alignL, top: below}; 1760 } 1761 else if (alignment === 'bottomRight') { 1762 offset = {left: alignR, top: below}; 1763 } 1764 else if (alignment === 'top') { 1765 offset = {left: (inst.options.isRTL || tooWide ? alignR : alignL), top: above}; 1766 } 1767 else { // bottom 1768 offset = {left: (inst.options.isRTL || tooWide ? alignR : alignL), 1769 top: (tooHigh ? above : below)}; 1770 } 1771 offset.left = Math.max((isFixed ? 0 : scrollX), offset.left); 1772 offset.top = Math.max((isFixed ? 0 : scrollY), offset.top); 1773 return offset; 1774 }, 1775 1776 /** Close date picker if clicked elsewhere. 1777 @private 1778 @param event {MouseEvent} The mouse click to check. */ 1779 _checkExternalClick: function(event) { 1780 if (!plugin.curInst) { 1781 return; 1782 } 1783 var elem = $(event.target); 1784 if (elem.closest('.' + plugin._popupClass + ',.' + plugin._triggerClass).length === 0 && 1785 !elem.hasClass(plugin._getMarker())) { 1786 plugin.hide(plugin.curInst); 1787 } 1788 }, 1789 1790 /** Hide a popup datepicker. 1791 @param elem {Element|object} The control to use or the current instance settings. 1792 @param immediate {boolean} <code>true</code> to close immediately without animation (internal). 1793 @example $(selector).datepick('hide') */ 1794 hide: function(elem, immediate) { 1795 if (!elem) { 1796 return; 1797 } 1798 var inst = this._getInst(elem); 1799 if ($.isEmptyObject(inst)) { 1800 inst = elem; 1801 } 1802 if (inst && inst === plugin.curInst) { 1803 var showAnim = (immediate ? '' : inst.options.showAnim); 1804 var showSpeed = inst.options.showSpeed; 1805 showSpeed = (showSpeed === 'normal' && $.ui && 1806 parseInt($.ui.version.substring(2)) >= 8 ? '_default' : showSpeed); 1807 var postProcess = function() { 1808 if (!inst.div) { 1809 return; 1810 } 1811 inst.div.remove(); 1812 inst.div = null; 1813 plugin.curInst = null; 1814 if ($.isFunction(inst.options.onClose)) { 1815 inst.options.onClose.apply(elem, [inst.selectedDates]); 1816 } 1817 }; 1818 inst.div.stop(); 1819 if ($.effects && ($.effects[showAnim] || ($.effects.effect && $.effects.effect[showAnim]))) { 1820 inst.div.hide(showAnim, inst.options.showOptions, showSpeed, postProcess); 1821 } 1822 else { 1823 var hideAnim = (showAnim === 'slideDown' ? 'slideUp' : 1824 (showAnim === 'fadeIn' ? 'fadeOut' : 'hide')); 1825 inst.div[hideAnim]((showAnim ? showSpeed : ''), postProcess); 1826 } 1827 if (!showAnim) { 1828 postProcess(); 1829 } 1830 } 1831 }, 1832 1833 /** Handle keystrokes in the datepicker. 1834 @private 1835 @param event {KeyEvent} The keystroke. 1836 @return {boolean} <code>true</code> if not handled, <code>false</code> if handled. */ 1837 _keyDown: function(event) { 1838 var elem = event.target; 1839 var inst = plugin._getInst(elem); 1840 var handled = false;
1841 if (inst.div) { 1842 if (event.keyCode === 9) { // Tab - close 1843 plugin.hide(elem); 1844 } 1845 else if (event.keyCode === 13) { // Enter - select 1846 plugin.selectDate(elem, 1847 $('a.' + inst.options.renderer.highlightedClass, inst.div)[0]); 1848 handled = true; 1849 } 1850 else { // Command keystrokes 1851 var commands = inst.options.commands; 1852 for (var name in commands) { 1853 var command = commands[name]; 1854 if (command.keystroke.keyCode === event.keyCode && 1855 !!command.keystroke.ctrlKey === !!(event.ctrlKey || event.metaKey) && 1856 !!command.keystroke.altKey === event.altKey && 1857 !!command.keystroke.shiftKey === event.shiftKey) { 1858 plugin.performAction(elem, name); 1859 handled = true; 1860 break; 1861 } 1862 } 1863 } 1864 } 1865 else { // Show on 'current' keystroke 1866 var command = inst.options.commands.current; 1867 if (command.keystroke.keyCode === event.keyCode && 1868 !!command.keystroke.ctrlKey === !!(event.ctrlKey || event.metaKey) && 1869 !!command.keystroke.altKey === event.altKey && 1870 !!command.keystroke.shiftKey === event.shiftKey) { 1871 plugin.show(elem); 1872 handled = true; 1873 } 1874 } 1875 inst.ctrlKey = ((event.keyCode < 48 && event.keyCode !== 32) || event.ctrlKey || event.metaKey); 1876 if (handled) { 1877 event.preventDefault(); 1878 event.stopPropagation(); 1879 } 1880 return !handled; 1881 }, 1882 1883 /** Filter keystrokes in the datepicker. 1884 @private 1885 @param event {KeyEvent} The keystroke. 1886 @return {boolean} <code>true</code> if allowed, <code>false</code> if not allowed. */ 1887 _keyPress: function(event) { 1888 var inst = plugin._getInst(event.target); 1889 if (!$.isEmptyObject(inst) && inst.options.constrainInput) { 1890 var ch = String.fromCharCode(event.keyCode || event.charCode); 1891 var allowedChars = plugin._allowedChars(inst); 1892 return (event.metaKey || inst.ctrlKey || ch < ' ' || 1893 !allowedChars || allowedChars.indexOf(ch) > -1); 1894 } 1895 return true; 1896 }, 1897 1898 /** Determine the set of characters allowed by the date format. 1899 @private 1900 @param inst {object} The current instance settings. 1901 @return {string} The set of allowed characters, or <code>null</code> if anything allowed. */ 1902 _allowedChars: function(inst) { 1903 var allowedChars = (inst.options.multiSelect ? inst.options.multiSeparator : 1904 (inst.options.rangeSelect ? inst.options.rangeSeparator : '')); 1905 var literal = false; 1906 var hasNum = false; 1907 var dateFormat = inst.options.dateFormat; 1908 for (var i = 0; i < dateFormat.length; i++) { 1909 var ch = dateFormat.charAt(i); 1910 if (literal) { 1911 if (ch === "'" && dateFormat.charAt(i + 1) !== "'") { 1912 literal = false; 1913 } 1914 else { 1915 allowedChars += ch; 1916 } 1917 } 1918 else { 1919 switch (ch) { 1920 case 'd': case 'm': case 'o': case 'w': 1921 allowedChars += (hasNum ? '' : '0123456789'); hasNum = true; break; 1922 case 'y': case '@': case '!': 1923 allowedChars += (hasNum ? '' : '0123456789') + '-'; hasNum = true; break; 1924 case 'J': 1925 allowedChars += (hasNum ? '' : '0123456789') + '-.'; hasNum = true; break; 1926 case 'D': case 'M': case 'Y': 1927 return null; // Accept anything 1928 case "'": 1929 if (dateFormat.charAt(i + 1) === "'") { 1930 allowedChars += "'"; 1931 } 1932 else { 1933 literal = true; 1934 } 1935 break; 1936 default: 1937 allowedChars += ch; 1938 } 1939 } 1940 } 1941 return allowedChars; 1942 }, 1943 1944 /** Synchronise datepicker with the field. 1945 @private 1946 @param event {KeyEvent} The keystroke. 1947 @return {boolean} <code>true</code> if allowed, <code>false</code> if not allowed. */ 1948 _keyUp: function(event) { 1949 var elem = event.target; 1950 var inst = plugin._getInst(elem); 1951 if (!$.isEmptyObject(inst) && !inst.ctrlKey && inst.lastVal !== inst.elem.val()) { 1952 try { 1953 var dates = plugin._extractDates(inst, inst.elem.val()); 1954 if (dates.length > 0) { 1955 plugin.setDate(elem, dates, null, true); 1956 } 1957 } 1958 catch (event) { 1959 // Ignore 1960 } 1961 } 1962 return true; 1963 }, 1964 1965 /** Increment/decrement month/year on mouse wheel activity. 1966 @private 1967 @param event {event} The mouse wheel event. 1968 @param delta {number} The amount of change. */ 1969 _doMouseWheel: function(event, delta) { 1970 var elem = (plugin.curInst && plugin.curInst.elem[0]) || 1971 $(event.target).closest('.' + plugin._getMarker())[0]; 1972 if (plugin.isDisabled(elem)) { 1973 return; 1974 } 1975 var inst = plugin._getInst(elem); 1976 if (inst.options.useMouseWheel) { 1977 delta = (delta < 0 ? -1 : +1); 1978 plugin.changeMonth(elem, -inst.options[event.ctrlKey ? 'monthsToJump' : 'monthsToStep'] * delta); 1979 } 1980 event.preventDefault(); 1981 }, 1982 1983 /** Clear an input and close a popup datepicker. 1984 @param elem {Element} The control to use. 1985 @example $(selector).datepick('clear') */ 1986 clear: function(elem) { 1987 var inst = this._getInst(elem); 1988 if (!$.isEmptyObject(inst)) { 1989 inst.selectedDates = []; 1990 this.hide(elem); 1991 var defaultDate = inst.get('defaultDate'); 1992 if (inst.options.selectDefaultDate && defaultDate) {
1993 this.setDate(elem, plugin.newDate(defaultDate || plugin.today())); 1994 } 1995 else { 1996 this._updateInput(elem); 1997 } 1998 } 1999 }, 2000 2001 /** Retrieve the selected date(s) for a datepicker. 2002 @param elem {Element} The control to examine. 2003 @return {Date[]} The selected date(s). 2004 @example var dates = $(selector).datepick('getDate') */ 2005 getDate: function(elem) { 2006 var inst = this._getInst(elem); 2007 return (!$.isEmptyObject(inst) ? inst.selectedDates : []); 2008 }, 2009 2010 /** Set the selected date(s) for a datepicker. 2011 @param elem {Element} the control to examine. 2012 @param dates {Date|number|string|array} the selected date(s). 2013 @param [endDate] {Date|number|string} the ending date for a range. 2014 @param keyUp {boolean} <code>true</code> if coming from <code>keyUp</code> processing (internal). 2015 @param setOpt {boolean} <code>true</code> if coming from option processing (internal). 2016 @example $(selector).datepick('setDate', new Date(2014, 12-1, 25)) 2017 $(selector).datepick('setDate', '12/25/2014', '01/01/2015') 2018 $(selector).datepick('setDate', [date1, date2, date3]) */ 2019 setDate: function(elem, dates, endDate, keyUp, setOpt) { 2020 var inst = this._getInst(elem); 2021 if (!$.isEmptyObject(inst)) { 2022 if (!$.isArray(dates)) { 2023 dates = [dates]; 2024 if (endDate) { 2025 dates.push(endDate); 2026 } 2027 } 2028 var minDate = inst.get('minDate'); 2029 var maxDate = inst.get('maxDate'); 2030 var curDate = inst.selectedDates[0]; 2031 inst.selectedDates = []; 2032 for (var i = 0; i < dates.length; i++) { 2033 var date = plugin.determineDate( 2034 dates[i], null, curDate, inst.options.dateFormat, inst.getConfig()); 2035 if (date) { 2036 if ((!minDate || date.getTime() >= minDate.getTime()) && 2037 (!maxDate || date.getTime() <= maxDate.getTime())) { 2038 var found = false; 2039 for (var j = 0; j < inst.selectedDates.length; j++) { 2040 if (inst.selectedDates[j].getTime() === date.getTime()) { 2041 found = true; 2042 break; 2043 } 2044 } 2045 if (!found) { 2046 inst.selectedDates.push(date); 2047 } 2048 } 2049 } 2050 } 2051 inst.selectedDates.splice(inst.options.multiSelect || 2052 (inst.options.rangeSelect ? 2 : 1), inst.selectedDates.length); 2053 if (inst.options.rangeSelect) { 2054 switch (inst.selectedDates.length) { 2055 case 1: inst.selectedDates[1] = inst.selectedDates[0]; break; 2056 case 2: inst.selectedDates[1] = 2057 (inst.selectedDates[0].getTime() > inst.selectedDates[1].getTime() ? 2058 inst.selectedDates[0] : inst.selectedDates[1]); break; 2059 } 2060 inst.pickingRange = false; 2061 } 2062 inst.prevDate = (inst.drawDate ? plugin.newDate(inst.drawDate) : null); 2063 inst.drawDate = this._checkMinMax(plugin.newDate(inst.selectedDates[0] || 2064 inst.get('defaultDate') || plugin.today()), inst); 2065 if (!setOpt) { 2066 this._update(elem); 2067 this._updateInput(elem, keyUp); 2068 } 2069 } 2070 }, 2071 2072 /** Determine whether a date is selectable for this datepicker. 2073 @private 2074 @param elem {Element} The control to check. 2075 @param date {Date|string|number} The date to check. 2076 @return {boolean} <code>true</code> if selectable, <code>false</code> if not. 2077 @example var selectable = $(selector).datepick('isSelectable', date) */ 2078 isSelectable: function(elem, date) { 2079 var inst = this._getInst(elem); 2080 if ($.isEmptyObject(inst)) { 2081 return false; 2082 } 2083 date = plugin.determineDate(date, inst.selectedDates[0] || this.today(), null, 2084 inst.options.dateFormat, inst.getConfig()); 2085 return this._isSelectable(elem, date, inst.options.onDate, 2086 inst.get('minDate'), inst.get('maxDate')); 2087 }, 2088 2089 /** Internally determine whether a date is selectable for this datepicker. 2090 @private 2091 @param elem {Element} the control to check. 2092 @param date {Date} The date to check. 2093 @param onDate {function|boolean} Any onDate callback or callback.selectable. 2094 @param minDate {Date} The minimum allowed date. 2095 @param maxDate {Date} The maximum allowed date. 2096 @return {boolean} <code>true</code> if selectable, <code>false</code> if not. */ 2097 _isSelectable: function(elem, date, onDate, minDate, maxDate) { 2098 var dateInfo = (typeof onDate === 'boolean' ? {selectable: onDate} : 2099 (!$.isFunction(onDate) ? {}
2099 : onDate.apply(elem, [date, true]))); 2100 return (dateInfo.selectable !== false) && 2101 (!minDate || date.getTime() >= minDate.getTime()) && 2102 (!maxDate || date.getTime() <= maxDate.getTime()); 2103 }, 2104 2105 /** Perform a named action for a datepicker. 2106 @param elem {element} The control to affect. 2107 @param action {string} The name of the action. */ 2108 performAction: function(elem, action) { 2109 var inst = this._getInst(elem); 2110 if (!$.isEmptyObject(inst) && !this.isDisabled(elem)) { 2111 var commands = inst.options.commands; 2112 if (commands[action] && commands[action].enabled.apply(elem, [inst])) { 2113 commands[action].action.apply(elem, [inst]); 2114 } 2115 } 2116 }, 2117 2118 /** Set the currently shown month, defaulting to today's. 2119 @param elem {Element} The control to affect. 2120 @param [year] {number} The year to show. 2121 @param [month] {number} The month to show (1-12). 2122 @param [day] {number} The day to show. 2123 @example $(selector).datepick('showMonth', 2014, 12, 25) */ 2124 showMonth: function(elem, year, month, day) { 2125 var inst = this._getInst(elem); 2126 if (!$.isEmptyObject(inst) && (day != null || 2127 (inst.drawDate.getFullYear() !== year || inst.drawDate.getMonth() + 1 !== month))) { 2128 inst.prevDate = plugin.newDate(inst.drawDate); 2129 var show = this._checkMinMax((year != null ? 2130 plugin.newDate(year, month, 1) : plugin.today()), inst); 2131 inst.drawDate = plugin.newDate(show.getFullYear(), show.getMonth() + 1, 2132 (day != null ? day : Math.min(inst.drawDate.getDate(), 2133 plugin.daysInMonth(show.getFullYear(), show.getMonth() + 1)))); 2134 this._update(elem); 2135 } 2136 }, 2137 2138 /** Adjust the currently shown month. 2139 @param elem {Element} The control to affect. 2140 @param offset {number} The number of months to change by. 2141 @example $(selector).datepick('changeMonth', 2)*/ 2142 changeMonth: function(elem, offset) { 2143 var inst = this._getInst(elem); 2144 if (!$.isEmptyObject(inst)) { 2145 var date = plugin.add(plugin.newDate(inst.drawDate), offset, 'm'); 2146 this.showMonth(elem, date.getFullYear(), date.getMonth() + 1); 2147 } 2148 }, 2149 2150 /** Adjust the currently shown day. 2151 @param elem {Element} The control to affect. 2152 @param offset {number} The number of days to change by. 2153 @example $(selector).datepick('changeDay', 7)*/ 2154 changeDay: function(elem, offset) { 2155 var inst = this._getInst(elem); 2156 if (!$.isEmptyObject(inst)) { 2157 var date = plugin.add(plugin.newDate(inst.drawDate), offset, 'd'); 2158 this.showMonth(elem, date.getFullYear(), date.getMonth() + 1, date.getDate()); 2159 } 2160 }, 2161 2162 /** Restrict a date to the minimum/maximum specified. 2163 @private 2164 @param date {Date} The date to check. 2165 @param inst {object} The current instance settings. */ 2166 _checkMinMax: function(date, inst) { 2167 var minDate = inst.get('minDate'); 2168 var maxDate = inst.get('maxDate'); 2169 date = (minDate && date.getTime() < minDate.getTime() ? plugin.newDate(minDate) : date); 2170 date = (maxDate && date.getTime() > maxDate.getTime() ? plugin.newDate(maxDate) : date); 2171 return date; 2172 }, 2173 2174 /** Retrieve the date associated with an entry in the datepicker. 2175 @param elem {Element} The control to examine. 2176 @param target {Element} The selected datepicker element. 2177 @return {Date} The corresponding date, or <code>null</code>. 2178 @example var date = $(selector).datepick('retrieveDate', $('div.datepick-popup a:contains(10)')[0]) */ 2179 retrieveDate: function(elem, target) { 2180 var inst = this._getInst(elem); 2181 return ($.isEmptyObject(inst) ? null : this._normaliseDate( 2182 new Date(parseInt(target.className.replace(/^.*dp(-?\d+).*$/, '$1'), 10)))); 2183 }, 2184 2185 /** Select a date for this datepicker. 2186 @param elem {Element} The control to examine. 2187 @param target {Element} The selected datepicker element. 2188 @example $(selector).datepick('selectDate', $('div.datepick-popup a:contains(10)')[0]) */ 2189 selectDate: function(elem, target) { 2190 var inst = this._getInst(elem); 2191 if (!$.isEmptyObject(inst) && !this.isDisabled(elem)) { 2192 var date = this.retrieveDate(elem, target); 2193 if (inst.options.multiSelect) { 2194 var found = false; 2195 for (var i = 0; i < inst.selectedDates.length; i++) { 2196 if (date.getTime() === inst.selectedDates[i].getTime()) { 2197 inst.selectedDates.splice(i, 1); 2198 found = true; 2199 break; 2200 } 2201 } 2202 if (!found && inst.selectedDates.length < inst.options.multiSelect) { 2203 inst.selectedDates.push(date); 2204 } 2205 } 2206 else if (inst.options.rangeSelect) { 2207 if (inst.pickingRange) { 2208 inst.selectedDates[1] = date; 2209 } 2210 else { 2211 inst.selectedDates = [date, date]; 2212 } 2213 inst.pickingRange = !inst.pickingRange; 2214 } 2215 else { 2216 inst.selectedDates = [date]; 2217 }
2218 inst.prevDate = plugin.newDate(date); 2219 this._updateInput(elem); 2220 if (inst.inline || inst.pickingRange || inst.selectedDates.length < 2221 (inst.options.multiSelect || (inst.options.rangeSelect ? 2 : 1))) { 2222 this._update(elem); 2223 } 2224 else { 2225 this.hide(elem); 2226 } 2227 } 2228 }, 2229 2230 /** Generate the datepicker content for this control. 2231 @private 2232 @param elem {Element} The control to affect. 2233 @param inst {object} The current instance settings. 2234 @return {jQuery} The datepicker content */ 2235 _generateContent: function(elem, inst) { 2236 var monthsToShow = inst.options.monthsToShow; 2237 monthsToShow = ($.isArray(monthsToShow) ? monthsToShow : [1, monthsToShow]); 2238 inst.drawDate = this._checkMinMax( 2239 inst.drawDate || inst.get('defaultDate') || plugin.today(), inst); 2240 var drawDate = plugin._applyMonthsOffset(plugin.newDate(inst.drawDate), inst); 2241 // Generate months 2242 var monthRows = ''; 2243 for (var row = 0; row < monthsToShow[0]; row++) { 2244 var months = ''; 2245 for (var col = 0; col < monthsToShow[1]; col++) { 2246 months += this._generateMonth(elem, inst, drawDate.getFullYear(), 2247 drawDate.getMonth() + 1, inst.options.renderer, (row === 0 && col === 0)); 2248 plugin.add(drawDate, 1, 'm'); 2249 } 2250 monthRows += this._prepare(inst.options.renderer.monthRow, inst).replace(/\{months\}/, months); 2251 } 2252 var picker = this._prepare(inst.options.renderer.picker, inst).replace(/\{months\}/, monthRows). 2253 replace(/\{weekHeader\}/g, this._generateDayHeaders(inst, inst.options.renderer)); 2254 // Add commands 2255 var addCommand = function(type, open, close, name, classes) { 2256 if (picker.indexOf('{' + type + ':' + name + '}') === -1) { 2257 return; 2258 } 2259 var command = inst.options.commands[name]; 2260 var date = (inst.options.commandsAsDateFormat ? command.date.apply(elem, [inst]) : null); 2261 picker = picker.replace(new RegExp('\\{' + type + ':' + name + '\\}', 'g'), 2262 '<' + open + (command.status ? ' title="' + inst.options[command.status] + '"' : '') + 2263 ' class="' + inst.options.renderer.commandClass + ' ' + 2264 inst.options.renderer.commandClass + '-' + name + ' ' + classes + 2265 (command.enabled(inst) ? '' : ' ' + inst.options.renderer.disabledClass) + '">' + 2266 (date ? plugin.formatDate(inst.options[command.text], date, inst.getConfig()) : 2267 inst.options[command.text]) + '</' + close + '>'); 2268 }; 2269 for (var name in inst.options.commands) { 2270 addCommand('button', 'button type="button"', 'button', name, 2271 inst.options.renderer.commandButtonClass); 2272 addCommand('link', 'a href="javascript:void(0)"', 'a', name, 2273 inst.options.renderer.commandLinkClass); 2274 } 2275 picker = $(picker); 2276 if (monthsToShow[1] > 1) { 2277 var count = 0; 2278 $(inst.options.renderer.monthSelector, picker).each(function() { 2279 var nth = ++count % monthsToShow[1];
2280 $(this).addClass(nth === 1 ? 'first' : (nth === 0 ? 'last' : '')); 2281 }); 2282 } 2283 // Add datepicker behaviour 2284 var self = this; 2285 picker.find(inst.options.renderer.daySelector + ' a').hover( 2286 function() { $(this).addClass(inst.options.renderer.highlightedClass); }, 2287 function() { 2288 (inst.inline ? $(this).closest('.' + self._getMarker()) : inst.div). 2289 find(inst.options.renderer.daySelector + ' a'). 2290 removeClass(inst.options.renderer.highlightedClass); 2291 }). 2292 click(function() { 2293 self.selectDate(elem, this); 2294 }).end(). 2295 find('select.' + this._monthYearClass + ':not(.' + this._anyYearClass + ')'). 2296 change(function() { 2297 var monthYear = $(this).val().split('/'); 2298 self.showMonth(elem, parseInt(monthYear[1], 10), parseInt(monthYear[0], 10)); 2299 }).end(). 2300 find('select.' + this._anyYearClass).click(function() { 2301 $(this).css('visibility', 'hidden'). 2302 next('input').css({left: this.offsetLeft, top: this.offsetTop, 2303 width: this.offsetWidth, height: this.offsetHeight}).show().focus(); 2304 }).end(). 2305 find('input.' + self._monthYearClass).change(function() { 2306 try { 2307 var year = parseInt($(this).val(), 10); 2308 year = (isNaN(year) ? inst.drawDate.getFullYear() : year); 2309 self.showMonth(elem, year, inst.drawDate.getMonth() + 1, inst.drawDate.getDate()); 2310 } 2311 catch (e) { 2312 alert(e); 2313 } 2314 }).keydown(function(event) { 2315 if (event.keyCode === 13) { // Enter 2316 $(event.elem).change(); 2317 } 2318 else if (event.keyCode === 27) { // Escape 2319 $(event.elem).hide().prev('select').css('visibility', 'visible'); 2320 inst.elem.focus(); 2321 } 2322 }); 2323 // Add command behaviour 2324 picker.find('.' + inst.options.renderer.commandClass).click(function() { 2325 if (!$(this).hasClass(inst.options.renderer.disabledClass)) { 2326 var action = this.className.replace( 2327 new RegExp('^.*' + inst.options.renderer.commandClass + '-([^ ]+).*$'), '$1'); 2328 plugin.performAction(elem, action); 2329 } 2330 }); 2331 // Add classes 2332 if (inst.options.isRTL) { 2333 picker.addClass(inst.options.renderer.rtlClass); 2334 } 2335 if (monthsToShow[0] * monthsToShow[1] > 1) { 2336 picker.addClass(inst.options.renderer.multiClass); 2337 } 2338 if (inst.options.pickerClass) { 2339 picker.addClass(inst.options.pickerClass); 2340 } 2341 // Resize 2342 $('body').append(picker); 2343 var width = 0; 2344 picker.find(inst.options.renderer.monthSelector).each(function() { 2345 width += $(this).outerWidth(); 2346 }); 2347 picker.width(width / monthsToShow[0]); 2348 // Pre-show customisation 2349 if ($.isFunction(inst.options.onShow)) { 2350 inst.options.onShow.apply(elem, [picker, inst]); 2351 } 2352 return picker; 2353 }, 2354 2355 /** Generate the content for a single month. 2356 @private 2357 @param elem {Element} The control to affect. 2358 @param inst {object} The current instance settings. 2359 @param year {number} The year to generate. 2360 @param month {number} The month to generate. 2361 @param renderer {object} The rendering templates. 2362 @param first {boolean} <code>true</code> if first of multiple months. 2363 @return {string} The month content. */ 2364 _generateMonth: function(elem, inst, year, month, renderer, first) { 2365 var daysInMonth = plugin.daysInMonth(year, month); 2366 var monthsToShow = inst.options.monthsToShow; 2367 monthsToShow = ($.isArray(monthsToShow) ? monthsToShow : [1, monthsToShow]); 2368 var fixedWeeks = inst.options.fixedWeeks || (monthsToShow[0] * monthsToShow[1] > 1); 2369 var firstDay = inst.options.firstDay; 2370 var leadDays = (plugin.newDate(year, month, 1).getDay() - firstDay + 7) % 7; 2371 var numWeeks = (fixedWeeks ? 6 : Math.ceil((leadDays + daysInMonth) / 7)); 2372 var selectOtherMonths = inst.options.selectOtherMonths && inst.options.showOtherMonths; 2373 var minDate = (inst.pickingRange ? inst.selectedDates[0] : inst.get('minDate')); 2374 var maxDate = inst.get('maxDate'); 2375 var showWeeks = renderer.week.indexOf('{weekOfYear}') > -1; 2376 var today = plugin.today(); 2377 var drawDate = plugin.newDate(year, month, 1); 2378 plugin.add(drawDate, -leadDays - (fixedWeeks && (drawDate.getDay() === firstDay) ? 7 : 0), 'd'); 2379 var ts = drawDate.getTime(); 2380 // Generate weeks 2381 var weeks = ''; 2382 for (var week = 0; week < numWeeks; week++) { 2383 var weekOfYear = (!showWeeks ? '' : '<span class="dp' + ts + '">' + 2384 ($.isFunction(inst.options.calculateWeek) ? inst.options.calculateWeek(drawDate) : 0) + '</span>'); 2385 var days = ''; 2386 for (var day = 0; day < 7; day++) { 2387 var selected = false;
2388 if (inst.options.rangeSelect && inst.selectedDates.length > 0) { 2389 selected = (drawDate.getTime() >= inst.selectedDates[0] && 2390 drawDate.getTime() <= inst.selectedDates[1]); 2391 } 2392 else { 2393 for (var i = 0; i < inst.selectedDates.length; i++) { 2394 if (inst.selectedDates[i].getTime() === drawDate.getTime()) { 2395 selected = true; 2396 break; 2397 } 2398 } 2399 } 2400 var dateInfo = (!$.isFunction(inst.options.onDate) ? {} : 2401 inst.options.onDate.apply(elem, [drawDate, drawDate.getMonth() + 1 === month])); 2402 var selectable = (selectOtherMonths || drawDate.getMonth() + 1 === month) && 2403 this._isSelectable(elem, drawDate, dateInfo.selectable, minDate, maxDate); 2404 days += this._prepare(renderer.day, inst).replace(/\{day\}/g, 2405 (selectable ? '<a href="javascript:void(0)"' : '<span') + 2406 ' class="dp' + ts + ' ' + (dateInfo.dateClass || '') + 2407 (selected && (selectOtherMonths || drawDate.getMonth() + 1 === month) ? 2408 ' ' + renderer.selectedClass : '') + 2409 (selectable ? ' ' + renderer.defaultClass : '') + 2410 ((drawDate.getDay() || 7) < 6 ? '' : ' ' + renderer.weekendClass) + 2411 (drawDate.getMonth() + 1 === month ? '' : ' ' + renderer.otherMonthClass) + 2412 (drawDate.getTime() === today.getTime() && (drawDate.getMonth() + 1) === month ? 2413 ' ' + renderer.todayClass : '') + 2414 (drawDate.getTime() === inst.drawDate.getTime() && (drawDate.getMonth() + 1) === month ? 2415 ' ' + renderer.highlightedClass : '') + '"' + 2416 (dateInfo.title || (inst.options.dayStatus && selectable) ? ' title="' + 2417 (dateInfo.title || plugin.formatDate( 2418 inst.options.dayStatus, drawDate, inst.getConfig())) + '"' : '') + '>' + 2419 (inst.options.showOtherMonths || (drawDate.getMonth() + 1) === month ? 2420 dateInfo.content || drawDate.getDate() : ' ') + 2421 (selectable ? '</a>' : '</span>')); 2422 plugin.add(drawDate, 1, 'd'); 2423 ts = drawDate.getTime(); 2424 } 2425 weeks += this._prepare(renderer.week, inst).replace(/\{days\}/g, days). 2426 replace(/\{weekOfYear\}/g, weekOfYear); 2427 } 2428 var monthHeader = this._prepare(renderer.month, inst).match(/\{monthHeader(:[^\}]+)?\}/); 2429 monthHeader = (monthHeader[0].length <= 13 ? 'MM yyyy' : 2430 monthHeader[0].substring(13, monthHeader[0].length - 1)); 2431 monthHeader = (first ? this._generateMonthSelection( 2432 inst, year, month, minDate, maxDate, monthHeader, renderer) : 2433 plugin.formatDate(monthHeader, plugin.newDate(year, month, 1), inst.getConfig())); 2434 var weekHeader = this._prepare(renderer.weekHeader, inst). 2435 replace(/\{days\}/g, this._generateDayHeaders(inst, renderer)); 2436 return this._prepare(renderer.month, inst).replace(/\{monthHeader(:[^\}]+)?\}/g, monthHeader). 2437 replace(/\{weekHeader\}/g, weekHeader).replace(/\{weeks\}/g, weeks); 2438 }, 2439 2440 /** Generate the HTML for the day headers. 2441 @private 2442 @param inst {object} The current instance settings. 2443 @param renderer {object} The rendering templates. 2444 @return {string} A week's worth of day headers. */ 2445 _generateDayHeaders: function(inst, renderer) { 2446 var header = ''; 2447 for (var day = 0; day < 7; day++) { 2448 var dow = (day + inst.options.firstDay) % 7; 2449 header += this._prepare(renderer.dayHeader, inst).replace(/\{day\}/g, 2450 '<span class="' + this._curDoWClass + dow + '" title="' + 2451 inst.options.dayNames[dow] + '">' + inst.options.dayNamesMin[dow] + '</span>'); 2452 } 2453 return header; 2454 }, 2455 2456 /** Generate selection controls for month. 2457 @private 2458 @param inst {object} The current instance settings. 2459 @param year {number} The year to generate. 2460 @param month {number} The month to generate. 2461 @param minDate {Date} The minimum date allowed. 2462 @param maxDate {Date} The maximum date allowed. 2463 @param monthHeader {string} The month/year format. 2464 @return {string} The month selection content. */ 2465 _generateMonthSelection: function(inst, year, month, minDate, maxDate, monthHeader) { 2466 if (!inst.options.changeMonth) { 2467 return plugin.formatDate( 2468 monthHeader, plugin.newDate(year, month, 1), inst.getConfig()); 2469 } 2470 // Months 2471 var monthNames = inst.options['monthNames' + (monthHeader.match(/mm/i) ? '' : 'Short')]; 2472 var html = monthHeader.replace(/m+/i, '\\x2E').replace(/y+/i, '\\x2F'); 2473 var selector = '<select class="' + this._monthYearClass + 2474 '" title="' + inst.options.monthStatus + '">'; 2475 for (var m = 1; m <= 12; m++) {
2476 if ((!minDate || plugin.newDate(year, m, plugin.daysInMonth(year, m)). 2477 getTime() >= minDate.getTime()) && 2478 (!maxDate || plugin.newDate(year, m, 1).getTime() <= maxDate.getTime())) { 2479 selector += '<option value="' + m + '/' + year + '"' + 2480 (month === m ? ' selected="selected"' : '') + '>' + 2481 monthNames[m - 1] + '</option>'; 2482 } 2483 } 2484 selector += '</select>'; 2485 html = html.replace(/\\x2E/, selector); 2486 // Years 2487 var yearRange = inst.options.yearRange; 2488 if (yearRange === 'any') { 2489 selector = '<select class="' + this._monthYearClass + ' ' + this._anyYearClass + 2490 '" title="' + inst.options.yearStatus + '">' + 2491 '<option>' + year + '</option></select>' + 2492 '<input class="' + this._monthYearClass + ' ' + this._curMonthClass + 2493 month + '" value="' + year + '">'; 2494 } 2495 else { 2496 yearRange = yearRange.split(':'); 2497 var todayYear = plugin.today().getFullYear(); 2498 var start = (yearRange[0].match('c[+-].*') ? year + parseInt(yearRange[0].substring(1), 10) : 2499 ((yearRange[0].match('[+-].*') ? todayYear : 0) + parseInt(yearRange[0], 10))); 2500 var end = (yearRange[1].match('c[+-].*') ? year + parseInt(yearRange[1].substring(1), 10) : 2501 ((yearRange[1].match('[+-].*') ? todayYear : 0) + parseInt(yearRange[1], 10))); 2502 selector = '<select class="' + this._monthYearClass + 2503 '" title="' + inst.options.yearStatus + '">'; 2504 start = plugin.add(plugin.newDate(start + 1, 1, 1), -1, 'd'); 2505 end = plugin.newDate(end, 1, 1); 2506 var addYear = function(y) { 2507 if (y !== 0) { 2508 selector += '<option value="' + month + '/' + y + '"' + 2509 (year === y ? ' selected="selected"' : '') + '>' + y + '</option>'; 2510 } 2511 }; 2512 if (start.getTime() < end.getTime()) { 2513 start = (minDate && minDate.getTime() > start.getTime() ? minDate : start).getFullYear(); 2514 end = (maxDate && maxDate.getTime() < end.getTime() ? maxDate : end).getFullYear(); 2515 for (var y = start; y <= end; y++) { 2516 addYear(y); 2517 } 2518 } 2519 else { 2520 start = (maxDate && maxDate.getTime() < start.getTime() ? maxDate : start).getFullYear(); 2521 end = (minDate && minDate.getTime() > end.getTime() ? minDate : end).getFullYear(); 2522 for (var y = start; y >= end; y--) { 2523 addYear(y); 2524 } 2525 } 2526 selector += '</select>'; 2527 } 2528 html = html.replace(/\\x2F/, selector); 2529 return html; 2530 }, 2531 2532 /** Prepare a render template for use. 2533 Exclude popup/inline sections that are not applicable. 2534 Localise text of the form: {l10n:name}. 2535 @private 2536 @param text {string} The text to localise. 2537 @param inst {object} The current instance settings. 2538 @return {string} The localised text. */ 2539 _prepare: function(text, inst) { 2540 var replaceSection = function(type, retain) { 2541 while (true) { 2542 var start = text.indexOf('{' + type + ':start}'); 2543 if (start === -1) { 2544 return; 2545 } 2546 var end = text.substring(start).indexOf('{' + type + ':end}'); 2547 if (end > -1) { 2548 text = text.substring(0, start) + 2549 (retain ? text.substr(start + type.length + 8, end - type.length - 8) : '') + 2550 text.substring(start + end + type.length + 6); 2551 } 2552 } 2553 }; 2554 replaceSection('inline', inst.inline); 2555 replaceSection('popup', !inst.inline); 2556 var pattern = /\{l10n:([^\}]+)\}/; 2557 var matches = null; 2558 while (matches = pattern.exec(text)) { 2559 text = text.replace(matches[0], inst.options[matches[1]]); 2560 } 2561 return text; 2562 } 2563 }); 2564 2565 var plugin = $.datepick; // Singleton instance 2566 2567 $(function() { 2568 $(document).on('mousedown.' + pluginName, plugin._checkExternalClick). 2569 on('resize.' + pluginName, function() { plugin.hide(plugin.curInst); }); 2570 }); 2571 2572})(jQuery); 2573
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.