1 | /* |
---|
2 | * jQuery UI Datepicker |
---|
3 | * |
---|
4 | * Copyright (c) 2006, 2007, 2008 Marc Grabanski |
---|
5 | * Dual licensed under the MIT (MIT-LICENSE.txt) |
---|
6 | * and GPL (GPL-LICENSE.txt) licenses. |
---|
7 | * |
---|
8 | * http://docs.jquery.com/UI/Datepicker |
---|
9 | * |
---|
10 | * Depends: |
---|
11 | * ui.core.js |
---|
12 | * |
---|
13 | * Marc Grabanski (m@marcgrabanski.com) and Keith Wood (kbwood@virginbroadband.com.au). |
---|
14 | */ |
---|
15 | |
---|
16 | (function($) { // hide the namespace |
---|
17 | |
---|
18 | var PROP_NAME = 'datepicker'; |
---|
19 | |
---|
20 | /* Date picker manager. |
---|
21 | Use the singleton instance of this class, $.datepicker, to interact with the date picker. |
---|
22 | Settings for (groups of) date pickers are maintained in an instance object, |
---|
23 | allowing multiple different settings on the same page. */ |
---|
24 | |
---|
25 | function Datepicker() { |
---|
26 | this.debug = false; // Change this to true to start debugging |
---|
27 | this._curInst = null; // The current instance in use |
---|
28 | this._disabledInputs = []; // List of date picker inputs that have been disabled |
---|
29 | this._datepickerShowing = false; // True if the popup picker is showing , false if not |
---|
30 | this._inDialog = false; // True if showing within a "dialog", false if not |
---|
31 | this._mainDivId = 'ui-datepicker-div'; // The ID of the main datepicker division |
---|
32 | this._appendClass = 'ui-datepicker-append'; // The name of the append marker class |
---|
33 | this._triggerClass = 'ui-datepicker-trigger'; // The name of the trigger marker class |
---|
34 | this._dialogClass = 'ui-datepicker-dialog'; // The name of the dialog marker class |
---|
35 | this._promptClass = 'ui-datepicker-prompt'; // The name of the dialog prompt marker class |
---|
36 | this._unselectableClass = 'ui-datepicker-unselectable'; // The name of the unselectable cell marker class |
---|
37 | this._currentClass = 'ui-datepicker-current-day'; // The name of the current day marker class |
---|
38 | this.regional = []; // Available regional settings, indexed by language code |
---|
39 | this.regional[''] = { // Default regional settings |
---|
40 | clearText: 'Clear', // Display text for clear link |
---|
41 | clearStatus: 'Erase the current date', // Status text for clear link |
---|
42 | closeText: 'Close', // Display text for close link |
---|
43 | closeStatus: 'Close without change', // Status text for close link |
---|
44 | prevText: '<Prev', // Display text for previous month link |
---|
45 | prevStatus: 'Show the previous month', // Status text for previous month link |
---|
46 | nextText: 'Next>', // Display text for next month link |
---|
47 | nextStatus: 'Show the next month', // Status text for next month link |
---|
48 | currentText: 'Today', // Display text for current month link |
---|
49 | currentStatus: 'Show the current month', // Status text for current month link |
---|
50 | monthNames: ['January','February','March','April','May','June', |
---|
51 | 'July','August','September','October','November','December'], // Names of months for drop-down and formatting |
---|
52 | monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], // For formatting |
---|
53 | monthStatus: 'Show a different month', // Status text for selecting a month |
---|
54 | yearStatus: 'Show a different year', // Status text for selecting a year |
---|
55 | weekHeader: 'Wk', // Header for the week of the year column |
---|
56 | weekStatus: 'Week of the year', // Status text for the week of the year column |
---|
57 | dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], // For formatting |
---|
58 | dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], // For formatting |
---|
59 | dayNamesMin: ['Su','Mo','Tu','We','Th','Fr','Sa'], // Column headings for days starting at Sunday |
---|
60 | dayStatus: 'Set DD as first week day', // Status text for the day of the week selection |
---|
61 | dateStatus: 'Select DD, M d', // Status text for the date selection |
---|
62 | dateFormat: 'mm/dd/yy', // See format options on parseDate |
---|
63 | firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ... |
---|
64 | initStatus: 'Select a date', // Initial Status text on opening |
---|
65 | isRTL: false // True if right-to-left language, false if left-to-right |
---|
66 | }; |
---|
67 | this._defaults = { // Global defaults for all the date picker instances |
---|
68 | showOn: 'focus', // 'focus' for popup on focus, |
---|
69 | // 'button' for trigger button, or 'both' for either |
---|
70 | showAnim: 'show', // Name of jQuery animation for popup |
---|
71 | showOptions: {}, // Options for enhanced animations |
---|
72 | defaultDate: null, // Used when field is blank: actual date, |
---|
73 | // +/-number for offset from today, null for today |
---|
74 | appendText: '', // Display text following the input box, e.g. showing the format |
---|
75 | buttonText: '...', // Text for trigger button |
---|
76 | buttonImage: '', // URL for trigger button image |
---|
77 | buttonImageOnly: false, // True if the image appears alone, false if it appears on a button |
---|
78 | closeAtTop: true, // True to have the clear/close at the top, |
---|
79 | // false to have them at the bottom |
---|
80 | mandatory: false, // True to hide the Clear link, false to include it |
---|
81 | hideIfNoPrevNext: false, // True to hide next/previous month links |
---|
82 | // if not applicable, false to just disable them |
---|
83 | navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links |
---|
84 | gotoCurrent: false, // True if today link goes back to current selection instead |
---|
85 | changeMonth: true, // True if month can be selected directly, false if only prev/next |
---|
86 | changeYear: true, // True if year can be selected directly, false if only prev/next |
---|
87 | yearRange: '-10:+10', // Range of years to display in drop-down, |
---|
88 | // either relative to current year (-nn:+nn) or absolute (nnnn:nnnn) |
---|
89 | changeFirstDay: true, // True to click on day name to change, false to remain as set |
---|
90 | highlightWeek: false, // True to highlight the selected week |
---|
91 | showOtherMonths: false, // True to show dates in other months, false to leave blank |
---|
92 | showWeeks: false, // True to show week of the year, false to omit |
---|
93 | calculateWeek: this.iso8601Week, // How to calculate the week of the year, |
---|
94 | // takes a Date and returns the number of the week for it |
---|
95 | shortYearCutoff: '+10', // Short year values < this are in the current century, |
---|
96 | // > this are in the previous century, |
---|
97 | // string value starting with '+' for current year + value |
---|
98 | showStatus: false, // True to show status bar at bottom, false to not show it |
---|
99 | statusForDate: this.dateStatus, // Function to provide status text for a date - |
---|
100 | // takes date and instance as parameters, returns display text |
---|
101 | minDate: null, // The earliest selectable date, or null for no limit |
---|
102 | maxDate: null, // The latest selectable date, or null for no limit |
---|
103 | duration: 'normal', // Duration of display/closure |
---|
104 | beforeShowDay: null, // Function that takes a date and returns an array with |
---|
105 | // [0] = true if selectable, false if not, [1] = custom CSS class name(s) or '', |
---|
106 | // [2] = cell title (optional), e.g. $.datepicker.noWeekends |
---|
107 | beforeShow: null, // Function that takes an input field and |
---|
108 | // returns a set of custom settings for the date picker |
---|
109 | onSelect: null, // Define a callback function when a date is selected |
---|
110 | onChangeMonthYear: null, // Define a callback function when the month or year is changed |
---|
111 | onClose: null, // Define a callback function when the datepicker is closed |
---|
112 | numberOfMonths: 1, // Number of months to show at a time |
---|
113 | stepMonths: 1, // Number of months to step back/forward |
---|
114 | rangeSelect: false, // Allows for selecting a date range on one date picker |
---|
115 | rangeSeparator: ' - ', // Text between two dates in a range |
---|
116 | altField: '', // Selector for an alternate field to store selected dates into |
---|
117 | altFormat: '' // The date format to use for the alternate field |
---|
118 | }; |
---|
119 | $.extend(this._defaults, this.regional['']); |
---|
120 | this.dpDiv = $('<div id="' + this._mainDivId + '" style="display: none;"></div>'); |
---|
121 | } |
---|
122 | |
---|
123 | $.extend(Datepicker.prototype, { |
---|
124 | /* Class name added to elements to indicate already configured with a date picker. */ |
---|
125 | markerClassName: 'hasDatepicker', |
---|
126 | |
---|
127 | /* Debug logging (if enabled). */ |
---|
128 | log: function () { |
---|
129 | if (this.debug) |
---|
130 | console.log.apply('', arguments); |
---|
131 | }, |
---|
132 | |
---|
133 | /* Override the default settings for all instances of the date picker. |
---|
134 | @param settings object - the new settings to use as defaults (anonymous object) |
---|
135 | @return the manager object */ |
---|
136 | setDefaults: function(settings) { |
---|
137 | extendRemove(this._defaults, settings || {}); |
---|
138 | return this; |
---|
139 | }, |
---|
140 | |
---|
141 | /* Attach the date picker to a jQuery selection. |
---|
142 | @param target element - the target input field or division or span |
---|
143 | @param settings object - the new settings to use for this date picker instance (anonymous) */ |
---|
144 | _attachDatepicker: function(target, settings) { |
---|
145 | // check for settings on the control itself - in namespace 'date:' |
---|
146 | var inlineSettings = null; |
---|
147 | for (attrName in this._defaults) { |
---|
148 | var attrValue = target.getAttribute('date:' + attrName); |
---|
149 | if (attrValue) { |
---|
150 | inlineSettings = inlineSettings || {}; |
---|
151 | try { |
---|
152 | inlineSettings[attrName] = eval(attrValue); |
---|
153 | } catch (err) { |
---|
154 | inlineSettings[attrName] = attrValue; |
---|
155 | } |
---|
156 | } |
---|
157 | } |
---|
158 | var nodeName = target.nodeName.toLowerCase(); |
---|
159 | var inline = (nodeName == 'div' || nodeName == 'span'); |
---|
160 | if (!target.id) |
---|
161 | target.id = 'dp' + new Date().getTime(); |
---|
162 | var inst = this._newInst($(target), inline); |
---|
163 | inst.settings = $.extend({}, settings || {}, inlineSettings || {}); |
---|
164 | if (nodeName == 'input') { |
---|
165 | this._connectDatepicker(target, inst); |
---|
166 | } else if (inline) { |
---|
167 | this._inlineDatepicker(target, inst); |
---|
168 | } |
---|
169 | }, |
---|
170 | |
---|
171 | /* Create a new instance object. */ |
---|
172 | _newInst: function(target, inline) { |
---|
173 | return {id: target[0].id, input: target, // associated target |
---|
174 | selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection |
---|
175 | drawMonth: 0, drawYear: 0, // month being drawn |
---|
176 | inline: inline, // is datepicker inline or not |
---|
177 | dpDiv: (!inline ? this.dpDiv : // presentation div |
---|
178 | $('<div class="ui-datepicker-inline"></div>'))}; |
---|
179 | }, |
---|
180 | |
---|
181 | /* Attach the date picker to an input field. */ |
---|
182 | _connectDatepicker: function(target, inst) { |
---|
183 | var input = $(target); |
---|
184 | if (input.hasClass(this.markerClassName)) |
---|
185 | return; |
---|
186 | var appendText = this._get(inst, 'appendText'); |
---|
187 | var isRTL = this._get(inst, 'isRTL'); |
---|
188 | if (appendText) |
---|
189 | input[isRTL ? 'before' : 'after']('<span class="' + this._appendClass + '">' + appendText + '</span>'); |
---|
190 | var showOn = this._get(inst, 'showOn'); |
---|
191 | if (showOn == 'focus' || showOn == 'both') // pop-up date picker when in the marked field |
---|
192 | input.focus(this._showDatepicker); |
---|
193 | if (showOn == 'button' || showOn == 'both') { // pop-up date picker when button clicked |
---|
194 | var buttonText = this._get(inst, 'buttonText'); |
---|
195 | var buttonImage = this._get(inst, 'buttonImage'); |
---|
196 | var trigger = $(this._get(inst, 'buttonImageOnly') ? |
---|
197 | $('<img/>').addClass(this._triggerClass). |
---|
198 | attr({ src: buttonImage, alt: buttonText, title: buttonText }) : |
---|
199 | $('<button type="button"></button>').addClass(this._triggerClass). |
---|
200 | html(buttonImage == '' ? buttonText : $('<img/>').attr( |
---|
201 | { src:buttonImage, alt:buttonText, title:buttonText }))); |
---|
202 | input[isRTL ? 'before' : 'after'](trigger); |
---|
203 | trigger.click(function() { |
---|
204 | if ($.datepicker._datepickerShowing && $.datepicker._lastInput == target) |
---|
205 | $.datepicker._hideDatepicker(); |
---|
206 | else |
---|
207 | $.datepicker._showDatepicker(target); |
---|
208 | return false; |
---|
209 | }); |
---|
210 | } |
---|
211 | input.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress). |
---|
212 | bind("setData.datepicker", function(event, key, value) { |
---|
213 | inst.settings[key] = value; |
---|
214 | }).bind("getData.datepicker", function(event, key) { |
---|
215 | return this._get(inst, key); |
---|
216 | }); |
---|
217 | $.data(target, PROP_NAME, inst); |
---|
218 | }, |
---|
219 | |
---|
220 | /* Attach an inline date picker to a div. */ |
---|
221 | _inlineDatepicker: function(target, inst) { |
---|
222 | var input = $(target); |
---|
223 | if (input.hasClass(this.markerClassName)) |
---|
224 | return; |
---|
225 | input.addClass(this.markerClassName).append(inst.dpDiv). |
---|
226 | bind("setData.datepicker", function(event, key, value){ |
---|
227 | inst.settings[key] = value; |
---|
228 | }).bind("getData.datepicker", function(event, key){ |
---|
229 | return this._get(inst, key); |
---|
230 | }); |
---|
231 | $.data(target, PROP_NAME, inst); |
---|
232 | this._setDate(inst, this._getDefaultDate(inst)); |
---|
233 | this._updateDatepicker(inst); |
---|
234 | }, |
---|
235 | |
---|
236 | /* Pop-up the date picker in a "dialog" box. |
---|
237 | @param input element - ignored |
---|
238 | @param dateText string - the initial date to display (in the current format) |
---|
239 | @param onSelect function - the function(dateText) to call when a date is selected |
---|
240 | @param settings object - update the dialog date picker instance's settings (anonymous object) |
---|
241 | @param pos int[2] - coordinates for the dialog's position within the screen or |
---|
242 | event - with x/y coordinates or |
---|
243 | leave empty for default (screen centre) |
---|
244 | @return the manager object */ |
---|
245 | _dialogDatepicker: function(input, dateText, onSelect, settings, pos) { |
---|
246 | var inst = this._dialogInst; // internal instance |
---|
247 | if (!inst) { |
---|
248 | var id = 'dp' + new Date().getTime(); |
---|
249 | this._dialogInput = $('<input type="text" id="' + id + |
---|
250 | '" size="1" style="position: absolute; top: -100px;"/>'); |
---|
251 | this._dialogInput.keydown(this._doKeyDown); |
---|
252 | $('body').append(this._dialogInput); |
---|
253 | inst = this._dialogInst = this._newInst(this._dialogInput, false); |
---|
254 | inst.settings = {}; |
---|
255 | $.data(this._dialogInput[0], PROP_NAME, inst); |
---|
256 | } |
---|
257 | extendRemove(inst.settings, settings || {}); |
---|
258 | this._dialogInput.val(dateText); |
---|
259 | |
---|
260 | this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null); |
---|
261 | if (!this._pos) { |
---|
262 | var browserWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; |
---|
263 | var browserHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; |
---|
264 | var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; |
---|
265 | var scrollY = document.documentElement.scrollTop || document.body.scrollTop; |
---|
266 | this._pos = // should use actual width/height below |
---|
267 | [(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY]; |
---|
268 | } |
---|
269 | |
---|
270 | // move input on screen for focus, but hidden behind dialog |
---|
271 | this._dialogInput.css('left', this._pos[0] + 'px').css('top', this._pos[1] + 'px'); |
---|
272 | inst.settings.onSelect = onSelect; |
---|
273 | this._inDialog = true; |
---|
274 | this.dpDiv.addClass(this._dialogClass); |
---|
275 | this._showDatepicker(this._dialogInput[0]); |
---|
276 | if ($.blockUI) |
---|
277 | $.blockUI(this.dpDiv); |
---|
278 | $.data(this._dialogInput[0], PROP_NAME, inst); |
---|
279 | return this; |
---|
280 | }, |
---|
281 | |
---|
282 | /* Detach a datepicker from its control. |
---|
283 | @param target element - the target input field or division or span */ |
---|
284 | _destroyDatepicker: function(target) { |
---|
285 | var nodeName = target.nodeName.toLowerCase(); |
---|
286 | var $target = $(target); |
---|
287 | $.removeData(target, PROP_NAME); |
---|
288 | if (nodeName == 'input') { |
---|
289 | $target.siblings('.' + this._appendClass).remove().end(). |
---|
290 | siblings('.' + this._triggerClass).remove().end(). |
---|
291 | removeClass(this.markerClassName). |
---|
292 | unbind('focus', this._showDatepicker). |
---|
293 | unbind('keydown', this._doKeyDown). |
---|
294 | unbind('keypress', this._doKeyPress); |
---|
295 | } else if (nodeName == 'div' || nodeName == 'span') |
---|
296 | $target.removeClass(this.markerClassName).empty(); |
---|
297 | }, |
---|
298 | |
---|
299 | /* Enable the date picker to a jQuery selection. |
---|
300 | @param target element - the target input field or division or span */ |
---|
301 | _enableDatepicker: function(target) { |
---|
302 | target.disabled = false; |
---|
303 | $(target).siblings('button.' + this._triggerClass).each(function() { this.disabled = false; }).end(). |
---|
304 | siblings('img.' + this._triggerClass).css({opacity: '1.0', cursor: ''}); |
---|
305 | this._disabledInputs = $.map(this._disabledInputs, |
---|
306 | function(value) { return (value == target ? null : value); }); // delete entry |
---|
307 | }, |
---|
308 | |
---|
309 | /* Disable the date picker to a jQuery selection. |
---|
310 | @param target element - the target input field or division or span */ |
---|
311 | _disableDatepicker: function(target) { |
---|
312 | target.disabled = true; |
---|
313 | $(target).siblings('button.' + this._triggerClass).each(function() { this.disabled = true; }).end(). |
---|
314 | siblings('img.' + this._triggerClass).css({opacity: '0.5', cursor: 'default'}); |
---|
315 | this._disabledInputs = $.map(this._disabledInputs, |
---|
316 | function(value) { return (value == target ? null : value); }); // delete entry |
---|
317 | this._disabledInputs[this._disabledInputs.length] = target; |
---|
318 | }, |
---|
319 | |
---|
320 | /* Is the first field in a jQuery collection disabled as a datepicker? |
---|
321 | @param target element - the target input field or division or span |
---|
322 | @return boolean - true if disabled, false if enabled */ |
---|
323 | _isDisabledDatepicker: function(target) { |
---|
324 | if (!target) |
---|
325 | return false; |
---|
326 | for (var i = 0; i < this._disabledInputs.length; i++) { |
---|
327 | if (this._disabledInputs[i] == target) |
---|
328 | return true; |
---|
329 | } |
---|
330 | return false; |
---|
331 | }, |
---|
332 | |
---|
333 | /* Update the settings for a date picker attached to an input field or division. |
---|
334 | @param target element - the target input field or division or span |
---|
335 | @param name object - the new settings to update or |
---|
336 | string - the name of the setting to change or |
---|
337 | @param value any - the new value for the setting (omit if above is an object) */ |
---|
338 | _changeDatepicker: function(target, name, value) { |
---|
339 | var settings = name || {}; |
---|
340 | if (typeof name == 'string') { |
---|
341 | settings = {}; |
---|
342 | settings[name] = value; |
---|
343 | } |
---|
344 | if (inst = $.data(target, PROP_NAME)) { |
---|
345 | extendRemove(inst.settings, settings); |
---|
346 | this._updateDatepicker(inst); |
---|
347 | } |
---|
348 | }, |
---|
349 | |
---|
350 | /* Set the dates for a jQuery selection. |
---|
351 | @param target element - the target input field or division or span |
---|
352 | @param date Date - the new date |
---|
353 | @param endDate Date - the new end date for a range (optional) */ |
---|
354 | _setDateDatepicker: function(target, date, endDate) { |
---|
355 | var inst = $.data(target, PROP_NAME); |
---|
356 | if (inst) { |
---|
357 | this._setDate(inst, date, endDate); |
---|
358 | this._updateDatepicker(inst); |
---|
359 | } |
---|
360 | }, |
---|
361 | |
---|
362 | /* Get the date(s) for the first entry in a jQuery selection. |
---|
363 | @param target element - the target input field or division or span |
---|
364 | @return Date - the current date or |
---|
365 | Date[2] - the current dates for a range */ |
---|
366 | _getDateDatepicker: function(target) { |
---|
367 | var inst = $.data(target, PROP_NAME); |
---|
368 | if (inst) |
---|
369 | this._setDateFromField(inst); |
---|
370 | return (inst ? this._getDate(inst) : null); |
---|
371 | }, |
---|
372 | |
---|
373 | /* Handle keystrokes. */ |
---|
374 | _doKeyDown: function(e) { |
---|
375 | var inst = $.data(e.target, PROP_NAME); |
---|
376 | var handled = true; |
---|
377 | if ($.datepicker._datepickerShowing) |
---|
378 | switch (e.keyCode) { |
---|
379 | case 9: $.datepicker._hideDatepicker(null, ''); |
---|
380 | break; // hide on tab out |
---|
381 | case 13: $.datepicker._selectDay(e.target, inst.selectedMonth, inst.selectedYear, |
---|
382 | $('td.ui-datepicker-days-cell-over', inst.dpDiv)[0]); |
---|
383 | return false; // don't submit the form |
---|
384 | break; // select the value on enter |
---|
385 | case 27: $.datepicker._hideDatepicker(null, $.datepicker._get(inst, 'duration')); |
---|
386 | break; // hide on escape |
---|
387 | case 33: $.datepicker._adjustDate(e.target, (e.ctrlKey ? -1 : |
---|
388 | -$.datepicker._get(inst, 'stepMonths')), (e.ctrlKey ? 'Y' : 'M')); |
---|
389 | break; // previous month/year on page up/+ ctrl |
---|
390 | case 34: $.datepicker._adjustDate(e.target, (e.ctrlKey ? +1 : |
---|
391 | +$.datepicker._get(inst, 'stepMonths')), (e.ctrlKey ? 'Y' : 'M')); |
---|
392 | break; // next month/year on page down/+ ctrl |
---|
393 | case 35: if (e.ctrlKey) $.datepicker._clearDate(e.target); |
---|
394 | break; // clear on ctrl+end |
---|
395 | case 36: if (e.ctrlKey) $.datepicker._gotoToday(e.target); |
---|
396 | break; // current on ctrl+home |
---|
397 | case 37: if (e.ctrlKey) $.datepicker._adjustDate(e.target, -1, 'D'); |
---|
398 | break; // -1 day on ctrl+left |
---|
399 | case 38: if (e.ctrlKey) $.datepicker._adjustDate(e.target, -7, 'D'); |
---|
400 | break; // -1 week on ctrl+up |
---|
401 | case 39: if (e.ctrlKey) $.datepicker._adjustDate(e.target, +1, 'D'); |
---|
402 | break; // +1 day on ctrl+right |
---|
403 | case 40: if (e.ctrlKey) $.datepicker._adjustDate(e.target, +7, 'D'); |
---|
404 | break; // +1 week on ctrl+down |
---|
405 | default: handled = false; |
---|
406 | } |
---|
407 | else if (e.keyCode == 36 && e.ctrlKey) // display the date picker on ctrl+home |
---|
408 | $.datepicker._showDatepicker(this); |
---|
409 | else |
---|
410 | handled = false; |
---|
411 | if (handled) { |
---|
412 | e.preventDefault(); |
---|
413 | e.stopPropagation(); |
---|
414 | } |
---|
415 | }, |
---|
416 | |
---|
417 | /* Filter entered characters - based on date format. */ |
---|
418 | _doKeyPress: function(e) { |
---|
419 | var inst = $.data(e.target, PROP_NAME); |
---|
420 | var chars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')); |
---|
421 | var chr = String.fromCharCode(e.charCode == undefined ? e.keyCode : e.charCode); |
---|
422 | return e.ctrlKey || (chr < ' ' || !chars || chars.indexOf(chr) > -1); |
---|
423 | }, |
---|
424 | |
---|
425 | /* Pop-up the date picker for a given input field. |
---|
426 | @param input element - the input field attached to the date picker or |
---|
427 | event - if triggered by focus */ |
---|
428 | _showDatepicker: function(input) { |
---|
429 | input = input.target || input; |
---|
430 | if (input.nodeName.toLowerCase() != 'input') // find from button/image trigger |
---|
431 | input = $('input', input.parentNode)[0]; |
---|
432 | if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput == input) // already here |
---|
433 | return; |
---|
434 | var inst = $.data(input, PROP_NAME); |
---|
435 | var beforeShow = $.datepicker._get(inst, 'beforeShow'); |
---|
436 | extendRemove(inst.settings, (beforeShow ? beforeShow.apply(input, [input, inst]) : {})); |
---|
437 | $.datepicker._hideDatepicker(null, ''); |
---|
438 | $.datepicker._lastInput = input; |
---|
439 | $.datepicker._setDateFromField(inst); |
---|
440 | if ($.datepicker._inDialog) // hide cursor |
---|
441 | input.value = ''; |
---|
442 | if (!$.datepicker._pos) { // position below input |
---|
443 | $.datepicker._pos = $.datepicker._findPos(input); |
---|
444 | $.datepicker._pos[1] += input.offsetHeight; // add the height |
---|
445 | } |
---|
446 | var isFixed = false; |
---|
447 | $(input).parents().each(function() { |
---|
448 | isFixed |= $(this).css('position') == 'fixed'; |
---|
449 | return !isFixed; |
---|
450 | }); |
---|
451 | if (isFixed && $.browser.opera) { // correction for Opera when fixed and scrolled |
---|
452 | $.datepicker._pos[0] -= document.documentElement.scrollLeft; |
---|
453 | $.datepicker._pos[1] -= document.documentElement.scrollTop; |
---|
454 | } |
---|
455 | var offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]}; |
---|
456 | $.datepicker._pos = null; |
---|
457 | inst.rangeStart = null; |
---|
458 | // determine sizing offscreen |
---|
459 | inst.dpDiv.css({position: 'absolute', display: 'block', top: '-1000px'}); |
---|
460 | $.datepicker._updateDatepicker(inst); |
---|
461 | // fix width for dynamic number of date pickers |
---|
462 | inst.dpDiv.width($.datepicker._getNumberOfMonths(inst)[1] * |
---|
463 | $('.ui-datepicker', inst.dpDiv[0])[0].offsetWidth); |
---|
464 | // and adjust position before showing |
---|
465 | offset = $.datepicker._checkOffset(inst, offset, isFixed); |
---|
466 | inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ? |
---|
467 | 'static' : (isFixed ? 'fixed' : 'absolute')), display: 'none', |
---|
468 | left: offset.left + 'px', top: offset.top + 'px'}); |
---|
469 | if (!inst.inline) { |
---|
470 | var showAnim = $.datepicker._get(inst, 'showAnim') || 'show'; |
---|
471 | var duration = $.datepicker._get(inst, 'duration'); |
---|
472 | var postProcess = function() { |
---|
473 | $.datepicker._datepickerShowing = true; |
---|
474 | if ($.browser.msie && parseInt($.browser.version) < 7) // fix IE < 7 select problems |
---|
475 | $('iframe.ui-datepicker-cover').css({width: inst.dpDiv.width() + 4, |
---|
476 | height: inst.dpDiv.height() + 4}); |
---|
477 | }; |
---|
478 | if ($.effects && $.effects[showAnim]) |
---|
479 | inst.dpDiv.show(showAnim, $.datepicker._get(inst, 'showOptions'), duration, postProcess); |
---|
480 | else |
---|
481 | inst.dpDiv[showAnim](duration, postProcess); |
---|
482 | if (duration == '') |
---|
483 | postProcess(); |
---|
484 | if (inst.input[0].type != 'hidden') |
---|
485 | inst.input[0].focus(); |
---|
486 | $.datepicker._curInst = inst; |
---|
487 | } |
---|
488 | }, |
---|
489 | |
---|
490 | /* Generate the date picker content. */ |
---|
491 | _updateDatepicker: function(inst) { |
---|
492 | var dims = {width: inst.dpDiv.width() + 4, |
---|
493 | height: inst.dpDiv.height() + 4}; |
---|
494 | inst.dpDiv.empty().append(this._generateDatepicker(inst)). |
---|
495 | find('iframe.ui-datepicker-cover'). |
---|
496 | css({width: dims.width, height: dims.height}); |
---|
497 | var numMonths = this._getNumberOfMonths(inst); |
---|
498 | inst.dpDiv[(numMonths[0] != 1 || numMonths[1] != 1 ? 'add' : 'remove') + |
---|
499 | 'Class']('ui-datepicker-multi'); |
---|
500 | inst.dpDiv[(this._get(inst, 'isRTL') ? 'add' : 'remove') + |
---|
501 | 'Class']('ui-datepicker-rtl'); |
---|
502 | if (inst.input && inst.input[0].type != 'hidden') |
---|
503 | $(inst.input[0]).focus(); |
---|
504 | }, |
---|
505 | |
---|
506 | /* Check positioning to remain on screen. */ |
---|
507 | _checkOffset: function(inst, offset, isFixed) { |
---|
508 | var pos = inst.input ? this._findPos(inst.input[0]) : null; |
---|
509 | var browserWidth = window.innerWidth || document.documentElement.clientWidth; |
---|
510 | var browserHeight = window.innerHeight || document.documentElement.clientHeight; |
---|
511 | var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft; |
---|
512 | var scrollY = document.documentElement.scrollTop || document.body.scrollTop; |
---|
513 | // reposition date picker horizontally if outside the browser window |
---|
514 | if (this._get(inst, 'isRTL') || (offset.left + inst.dpDiv.width() - scrollX) > browserWidth) |
---|
515 | offset.left = Math.max((isFixed ? 0 : scrollX), |
---|
516 | pos[0] + (inst.input ? inst.input.width() : 0) - (isFixed ? scrollX : 0) - inst.dpDiv.width() - |
---|
517 | (isFixed && $.browser.opera ? document.documentElement.scrollLeft : 0)); |
---|
518 | else |
---|
519 | offset.left -= (isFixed ? scrollX : 0); |
---|
520 | // reposition date picker vertically if outside the browser window |
---|
521 | if ((offset.top + inst.dpDiv.height() - scrollY) > browserHeight) |
---|
522 | offset.top = Math.max((isFixed ? 0 : scrollY), |
---|
523 | pos[1] - (isFixed ? scrollY : 0) - (this._inDialog ? 0 : inst.dpDiv.height()) - |
---|
524 | (isFixed && $.browser.opera ? document.documentElement.scrollTop : 0)); |
---|
525 | else |
---|
526 | offset.top -= (isFixed ? scrollY : 0); |
---|
527 | return offset; |
---|
528 | }, |
---|
529 | |
---|
530 | /* Find an object's position on the screen. */ |
---|
531 | _findPos: function(obj) { |
---|
532 | while (obj && (obj.type == 'hidden' || obj.nodeType != 1)) { |
---|
533 | obj = obj.nextSibling; |
---|
534 | } |
---|
535 | var position = $(obj).offset(); |
---|
536 | return [position.left, position.top]; |
---|
537 | }, |
---|
538 | |
---|
539 | /* Hide the date picker from view. |
---|
540 | @param input element - the input field attached to the date picker |
---|
541 | @param duration string - the duration over which to close the date picker */ |
---|
542 | _hideDatepicker: function(input, duration) { |
---|
543 | var inst = this._curInst; |
---|
544 | if (!inst) |
---|
545 | return; |
---|
546 | var rangeSelect = this._get(inst, 'rangeSelect'); |
---|
547 | if (rangeSelect && this._stayOpen) |
---|
548 | this._selectDate('#' + inst.id, this._formatDate(inst, |
---|
549 | inst.currentDay, inst.currentMonth, inst.currentYear)); |
---|
550 | this._stayOpen = false; |
---|
551 | if (this._datepickerShowing) { |
---|
552 | duration = (duration != null ? duration : this._get(inst, 'duration')); |
---|
553 | var showAnim = this._get(inst, 'showAnim'); |
---|
554 | var postProcess = function() { |
---|
555 | $.datepicker._tidyDialog(inst); |
---|
556 | }; |
---|
557 | if (duration != '' && $.effects && $.effects[showAnim]) |
---|
558 | inst.dpDiv.hide(showAnim, $.datepicker._get(inst, 'showOptions'), |
---|
559 | duration, postProcess); |
---|
560 | else |
---|
561 | inst.dpDiv[(duration == '' ? 'hide' : (showAnim == 'slideDown' ? 'slideUp' : |
---|
562 | (showAnim == 'fadeIn' ? 'fadeOut' : 'hide')))](duration, postProcess); |
---|
563 | if (duration == '') |
---|
564 | this._tidyDialog(inst); |
---|
565 | var onClose = this._get(inst, 'onClose'); |
---|
566 | if (onClose) |
---|
567 | onClose.apply((inst.input ? inst.input[0] : null), |
---|
568 | [this._getDate(inst), inst]); // trigger custom callback |
---|
569 | this._datepickerShowing = false; |
---|
570 | this._lastInput = null; |
---|
571 | inst.settings.prompt = null; |
---|
572 | if (this._inDialog) { |
---|
573 | this._dialogInput.css({ position: 'absolute', left: '0', top: '-100px' }); |
---|
574 | if ($.blockUI) { |
---|
575 | $.unblockUI(); |
---|
576 | $('body').append(this.dpDiv); |
---|
577 | } |
---|
578 | } |
---|
579 | this._inDialog = false; |
---|
580 | } |
---|
581 | this._curInst = null; |
---|
582 | }, |
---|
583 | |
---|
584 | /* Tidy up after a dialog display. */ |
---|
585 | _tidyDialog: function(inst) { |
---|
586 | inst.dpDiv.removeClass(this._dialogClass).unbind('.ui-datepicker'); |
---|
587 | $('.' + this._promptClass, inst.dpDiv).remove(); |
---|
588 | }, |
---|
589 | |
---|
590 | /* Close date picker if clicked elsewhere. */ |
---|
591 | _checkExternalClick: function(event) { |
---|
592 | if (!$.datepicker._curInst) |
---|
593 | return; |
---|
594 | var $target = $(event.target); |
---|
595 | if (($target.parents('#' + $.datepicker._mainDivId).length == 0) && |
---|
596 | !$target.hasClass($.datepicker.markerClassName) && |
---|
597 | !$target.hasClass($.datepicker._triggerClass) && |
---|
598 | $.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI)) |
---|
599 | $.datepicker._hideDatepicker(null, ''); |
---|
600 | }, |
---|
601 | |
---|
602 | /* Adjust one of the date sub-fields. */ |
---|
603 | _adjustDate: function(id, offset, period) { |
---|
604 | var target = $(id); |
---|
605 | var inst = $.data(target[0], PROP_NAME); |
---|
606 | this._adjustInstDate(inst, offset, period); |
---|
607 | this._updateDatepicker(inst); |
---|
608 | }, |
---|
609 | |
---|
610 | /* Action for current link. */ |
---|
611 | _gotoToday: function(id) { |
---|
612 | var target = $(id); |
---|
613 | var inst = $.data(target[0], PROP_NAME); |
---|
614 | if (this._get(inst, 'gotoCurrent') && inst.currentDay) { |
---|
615 | inst.selectedDay = inst.currentDay; |
---|
616 | inst.drawMonth = inst.selectedMonth = inst.currentMonth; |
---|
617 | inst.drawYear = inst.selectedYear = inst.currentYear; |
---|
618 | } |
---|
619 | else { |
---|
620 | var date = new Date(); |
---|
621 | inst.selectedDay = date.getDate(); |
---|
622 | inst.drawMonth = inst.selectedMonth = date.getMonth(); |
---|
623 | inst.drawYear = inst.selectedYear = date.getFullYear(); |
---|
624 | } |
---|
625 | this._adjustDate(target); |
---|
626 | this._notifyChange(inst); |
---|
627 | }, |
---|
628 | |
---|
629 | /* Action for selecting a new month/year. */ |
---|
630 | _selectMonthYear: function(id, select, period) { |
---|
631 | var target = $(id); |
---|
632 | var inst = $.data(target[0], PROP_NAME); |
---|
633 | inst._selectingMonthYear = false; |
---|
634 | inst[period == 'M' ? 'drawMonth' : 'drawYear'] = |
---|
635 | select.options[select.selectedIndex].value - 0; |
---|
636 | this._adjustDate(target); |
---|
637 | this._notifyChange(inst); |
---|
638 | }, |
---|
639 | |
---|
640 | /* Restore input focus after not changing month/year. */ |
---|
641 | _clickMonthYear: function(id) { |
---|
642 | var target = $(id); |
---|
643 | var inst = $.data(target[0], PROP_NAME); |
---|
644 | if (inst.input && inst._selectingMonthYear && !$.browser.msie) |
---|
645 | inst.input[0].focus(); |
---|
646 | inst._selectingMonthYear = !inst._selectingMonthYear; |
---|
647 | }, |
---|
648 | |
---|
649 | /* Action for changing the first week day. */ |
---|
650 | _changeFirstDay: function(id, day) { |
---|
651 | var target = $(id); |
---|
652 | var inst = $.data(target[0], PROP_NAME); |
---|
653 | inst.settings.firstDay = day; |
---|
654 | this._updateDatepicker(inst); |
---|
655 | }, |
---|
656 | |
---|
657 | /* Action for selecting a day. */ |
---|
658 | _selectDay: function(id, month, year, td) { |
---|
659 | if ($(td).hasClass(this._unselectableClass)) |
---|
660 | return; |
---|
661 | var target = $(id); |
---|
662 | var inst = $.data(target[0], PROP_NAME); |
---|
663 | var rangeSelect = this._get(inst, 'rangeSelect'); |
---|
664 | if (rangeSelect) { |
---|
665 | this._stayOpen = !this._stayOpen; |
---|
666 | if (this._stayOpen) { |
---|
667 | $('.ui-datepicker td').removeClass(this._currentClass); |
---|
668 | $(td).addClass(this._currentClass); |
---|
669 | } |
---|
670 | } |
---|
671 | inst.selectedDay = inst.currentDay = $('a', td).html(); |
---|
672 | inst.selectedMonth = inst.currentMonth = month; |
---|
673 | inst.selectedYear = inst.currentYear = year; |
---|
674 | if (this._stayOpen) { |
---|
675 | inst.endDay = inst.endMonth = inst.endYear = null; |
---|
676 | } |
---|
677 | else if (rangeSelect) { |
---|
678 | inst.endDay = inst.currentDay; |
---|
679 | inst.endMonth = inst.currentMonth; |
---|
680 | inst.endYear = inst.currentYear; |
---|
681 | } |
---|
682 | this._selectDate(id, this._formatDate(inst, |
---|
683 | inst.currentDay, inst.currentMonth, inst.currentYear)); |
---|
684 | if (this._stayOpen) { |
---|
685 | inst.rangeStart = this._daylightSavingAdjust( |
---|
686 | new Date(inst.currentYear, inst.currentMonth, inst.currentDay)); |
---|
687 | this._updateDatepicker(inst); |
---|
688 | } |
---|
689 | else if (rangeSelect) { |
---|
690 | inst.selectedDay = inst.currentDay = inst.rangeStart.getDate(); |
---|
691 | inst.selectedMonth = inst.currentMonth = inst.rangeStart.getMonth(); |
---|
692 | inst.selectedYear = inst.currentYear = inst.rangeStart.getFullYear(); |
---|
693 | inst.rangeStart = null; |
---|
694 | if (inst.inline) |
---|
695 | this._updateDatepicker(inst); |
---|
696 | } |
---|
697 | }, |
---|
698 | |
---|
699 | /* Erase the input field and hide the date picker. */ |
---|
700 | _clearDate: function(id) { |
---|
701 | var target = $(id); |
---|
702 | var inst = $.data(target[0], PROP_NAME); |
---|
703 | if (this._get(inst, 'mandatory')) |
---|
704 | return; |
---|
705 | this._stayOpen = false; |
---|
706 | inst.endDay = inst.endMonth = inst.endYear = inst.rangeStart = null; |
---|
707 | this._selectDate(target, ''); |
---|
708 | }, |
---|
709 | |
---|
710 | /* Update the input field with the selected date. */ |
---|
711 | _selectDate: function(id, dateStr) { |
---|
712 | var target = $(id); |
---|
713 | var inst = $.data(target[0], PROP_NAME); |
---|
714 | dateStr = (dateStr != null ? dateStr : this._formatDate(inst)); |
---|
715 | if (this._get(inst, 'rangeSelect') && dateStr) |
---|
716 | dateStr = (inst.rangeStart ? this._formatDate(inst, inst.rangeStart) : |
---|
717 | dateStr) + this._get(inst, 'rangeSeparator') + dateStr; |
---|
718 | if (inst.input) |
---|
719 | inst.input.val(dateStr); |
---|
720 | this._updateAlternate(inst); |
---|
721 | var onSelect = this._get(inst, 'onSelect'); |
---|
722 | if (onSelect) |
---|
723 | onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]); // trigger custom callback |
---|
724 | else if (inst.input) |
---|
725 | inst.input.trigger('change'); // fire the change event |
---|
726 | if (inst.inline) |
---|
727 | this._updateDatepicker(inst); |
---|
728 | else if (!this._stayOpen) { |
---|
729 | this._hideDatepicker(null, this._get(inst, 'duration')); |
---|
730 | this._lastInput = inst.input[0]; |
---|
731 | if (typeof(inst.input[0]) != 'object') |
---|
732 | inst.input[0].focus(); // restore focus |
---|
733 | this._lastInput = null; |
---|
734 | } |
---|
735 | }, |
---|
736 | |
---|
737 | /* Update any alternate field to synchronise with the main field. */ |
---|
738 | _updateAlternate: function(inst) { |
---|
739 | var altField = this._get(inst, 'altField'); |
---|
740 | if (altField) { // update alternate field too |
---|
741 | var altFormat = this._get(inst, 'altFormat'); |
---|
742 | var date = this._getDate(inst); |
---|
743 | dateStr = (isArray(date) ? (!date[0] && !date[1] ? '' : |
---|
744 | this.formatDate(altFormat, date[0], this._getFormatConfig(inst)) + |
---|
745 | this._get(inst, 'rangeSeparator') + this.formatDate( |
---|
746 | altFormat, date[1] || date[0], this._getFormatConfig(inst))) : |
---|
747 | this.formatDate(altFormat, date, this._getFormatConfig(inst))); |
---|
748 | $(altField).each(function() { $(this).val(dateStr); }); |
---|
749 | } |
---|
750 | }, |
---|
751 | |
---|
752 | /* Set as beforeShowDay function to prevent selection of weekends. |
---|
753 | @param date Date - the date to customise |
---|
754 | @return [boolean, string] - is this date selectable?, what is its CSS class? */ |
---|
755 | noWeekends: function(date) { |
---|
756 | var day = date.getDay(); |
---|
757 | return [(day > 0 && day < 6), '']; |
---|
758 | }, |
---|
759 | |
---|
760 | /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. |
---|
761 | @param date Date - the date to get the week for |
---|
762 | @return number - the number of the week within the year that contains this date */ |
---|
763 | iso8601Week: function(date) { |
---|
764 | var checkDate = new Date(date.getFullYear(), date.getMonth(), date.getDate()); |
---|
765 | var firstMon = new Date(checkDate.getFullYear(), 1 - 1, 4); // First week always contains 4 Jan |
---|
766 | var firstDay = firstMon.getDay() || 7; // Day of week: Mon = 1, ..., Sun = 7 |
---|
767 | firstMon.setDate(firstMon.getDate() + 1 - firstDay); // Preceding Monday |
---|
768 | if (firstDay < 4 && checkDate < firstMon) { // Adjust first three days in year if necessary |
---|
769 | checkDate.setDate(checkDate.getDate() - 3); // Generate for previous year |
---|
770 | return $.datepicker.iso8601Week(checkDate); |
---|
771 | } else if (checkDate > new Date(checkDate.getFullYear(), 12 - 1, 28)) { // Check last three days in year |
---|
772 | firstDay = new Date(checkDate.getFullYear() + 1, 1 - 1, 4).getDay() || 7; |
---|
773 | if (firstDay > 4 && (checkDate.getDay() || 7) < firstDay - 3) { // Adjust if necessary |
---|
774 | checkDate.setDate(checkDate.getDate() + 3); // Generate for next year |
---|
775 | return $.datepicker.iso8601Week(checkDate); |
---|
776 | } |
---|
777 | } |
---|
778 | return Math.floor(((checkDate - firstMon) / 86400000) / 7) + 1; // Weeks to given date |
---|
779 | }, |
---|
780 | |
---|
781 | /* Provide status text for a particular date. |
---|
782 | @param date the date to get the status for |
---|
783 | @param inst the current datepicker instance |
---|
784 | @return the status display text for this date */ |
---|
785 | dateStatus: function(date, inst) { |
---|
786 | return $.datepicker.formatDate($.datepicker._get(inst, 'dateStatus'), |
---|
787 | date, $.datepicker._getFormatConfig(inst)); |
---|
788 | }, |
---|
789 | |
---|
790 | /* Parse a string value into a date object. |
---|
791 | See formatDate below for the possible formats. |
---|
792 | |
---|
793 | @param format string - the expected format of the date |
---|
794 | @param value string - the date in the above format |
---|
795 | @param settings Object - attributes include: |
---|
796 | shortYearCutoff number - the cutoff year for determining the century (optional) |
---|
797 | dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) |
---|
798 | dayNames string[7] - names of the days from Sunday (optional) |
---|
799 | monthNamesShort string[12] - abbreviated names of the months (optional) |
---|
800 | monthNames string[12] - names of the months (optional) |
---|
801 | @return Date - the extracted date value or null if value is blank */ |
---|
802 | parseDate: function (format, value, settings) { |
---|
803 | if (format == null || value == null) |
---|
804 | throw 'Invalid arguments'; |
---|
805 | value = (typeof value == 'object' ? value.toString() : value + ''); |
---|
806 | if (value == '') |
---|
807 | return null; |
---|
808 | var shortYearCutoff = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff; |
---|
809 | var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort; |
---|
810 | var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames; |
---|
811 | var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort; |
---|
812 | var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames; |
---|
813 | var year = -1; |
---|
814 | var month = -1; |
---|
815 | var day = -1; |
---|
816 | var literal = false; |
---|
817 | // Check whether a format character is doubled |
---|
818 | var lookAhead = function(match) { |
---|
819 | var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match); |
---|
820 | if (matches) |
---|
821 | iFormat++; |
---|
822 | return matches; |
---|
823 | }; |
---|
824 | // Extract a number from the string value |
---|
825 | var getNumber = function(match) { |
---|
826 | lookAhead(match); |
---|
827 | var origSize = (match == '@' ? 14 : (match == 'y' ? 4 : 2)); |
---|
828 | var size = origSize; |
---|
829 | var num = 0; |
---|
830 | while (size > 0 && iValue < value.length && |
---|
831 | value.charAt(iValue) >= '0' && value.charAt(iValue) <= '9') { |
---|
832 | num = num * 10 + (value.charAt(iValue++) - 0); |
---|
833 | size--; |
---|
834 | } |
---|
835 | if (size == origSize) |
---|
836 | throw 'Missing number at position ' + iValue; |
---|
837 | return num; |
---|
838 | }; |
---|
839 | // Extract a name from the string value and convert to an index |
---|
840 | var getName = function(match, shortNames, longNames) { |
---|
841 | var names = (lookAhead(match) ? longNames : shortNames); |
---|
842 | var size = 0; |
---|
843 | for (var j = 0; j < names.length; j++) |
---|
844 | size = Math.max(size, names[j].length); |
---|
845 | var name = ''; |
---|
846 | var iInit = iValue; |
---|
847 | while (size > 0 && iValue < value.length) { |
---|
848 | name += value.charAt(iValue++); |
---|
849 | for (var i = 0; i < names.length; i++) |
---|
850 | if (name == names[i]) |
---|
851 | return i + 1; |
---|
852 | size--; |
---|
853 | } |
---|
854 | throw 'Unknown name at position ' + iInit; |
---|
855 | }; |
---|
856 | // Confirm that a literal character matches the string value |
---|
857 | var checkLiteral = function() { |
---|
858 | if (value.charAt(iValue) != format.charAt(iFormat)) |
---|
859 | throw 'Unexpected literal at position ' + iValue; |
---|
860 | iValue++; |
---|
861 | }; |
---|
862 | var iValue = 0; |
---|
863 | for (var iFormat = 0; iFormat < format.length; iFormat++) { |
---|
864 | if (literal) |
---|
865 | if (format.charAt(iFormat) == "'" && !lookAhead("'")) |
---|
866 | literal = false; |
---|
867 | else |
---|
868 | checkLiteral(); |
---|
869 | else |
---|
870 | switch (format.charAt(iFormat)) { |
---|
871 | case 'd': |
---|
872 | day = getNumber('d'); |
---|
873 | break; |
---|
874 | case 'D': |
---|
875 | getName('D', dayNamesShort, dayNames); |
---|
876 | break; |
---|
877 | case 'm': |
---|
878 | month = getNumber('m'); |
---|
879 | break; |
---|
880 | case 'M': |
---|
881 | month = getName('M', monthNamesShort, monthNames); |
---|
882 | break; |
---|
883 | case 'y': |
---|
884 | year = getNumber('y'); |
---|
885 | break; |
---|
886 | case '@': |
---|
887 | var date = new Date(getNumber('@')); |
---|
888 | year = date.getFullYear(); |
---|
889 | month = date.getMonth() + 1; |
---|
890 | day = date.getDate(); |
---|
891 | break; |
---|
892 | case "'": |
---|
893 | if (lookAhead("'")) |
---|
894 | checkLiteral(); |
---|
895 | else |
---|
896 | literal = true; |
---|
897 | break; |
---|
898 | default: |
---|
899 | checkLiteral(); |
---|
900 | } |
---|
901 | } |
---|
902 | if (year < 100) |
---|
903 | year += new Date().getFullYear() - new Date().getFullYear() % 100 + |
---|
904 | (year <= shortYearCutoff ? 0 : -100); |
---|
905 | var date = this._daylightSavingAdjust(new Date(year, month - 1, day)); |
---|
906 | if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != day) |
---|
907 | throw 'Invalid date'; // E.g. 31/02/* |
---|
908 | return date; |
---|
909 | }, |
---|
910 | |
---|
911 | /* Standard date formats. */ |
---|
912 | ATOM: 'yy-mm-dd', // RFC 3339 (ISO 8601) |
---|
913 | COOKIE: 'D, dd M yy', |
---|
914 | ISO_8601: 'yy-mm-dd', |
---|
915 | RFC_822: 'D, d M y', |
---|
916 | RFC_850: 'DD, dd-M-y', |
---|
917 | RFC_1036: 'D, d M y', |
---|
918 | RFC_1123: 'D, d M yy', |
---|
919 | RFC_2822: 'D, d M yy', |
---|
920 | RSS: 'D, d M y', // RFC 822 |
---|
921 | TIMESTAMP: '@', |
---|
922 | W3C: 'yy-mm-dd', // ISO 8601 |
---|
923 | |
---|
924 | /* Format a date object into a string value. |
---|
925 | The format can be combinations of the following: |
---|
926 | d - day of month (no leading zero) |
---|
927 | dd - day of month (two digit) |
---|
928 | D - day name short |
---|
929 | DD - day name long |
---|
930 | m - month of year (no leading zero) |
---|
931 | mm - month of year (two digit) |
---|
932 | M - month name short |
---|
933 | MM - month name long |
---|
934 | y - year (two digit) |
---|
935 | yy - year (four digit) |
---|
936 | @ - Unix timestamp (ms since 01/01/1970) |
---|
937 | '...' - literal text |
---|
938 | '' - single quote |
---|
939 | |
---|
940 | @param format string - the desired format of the date |
---|
941 | @param date Date - the date value to format |
---|
942 | @param settings Object - attributes include: |
---|
943 | dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) |
---|
944 | dayNames string[7] - names of the days from Sunday (optional) |
---|
945 | monthNamesShort string[12] - abbreviated names of the months (optional) |
---|
946 | monthNames string[12] - names of the months (optional) |
---|
947 | @return string - the date in the above format */ |
---|
948 | formatDate: function (format, date, settings) { |
---|
949 | if (!date) |
---|
950 | return ''; |
---|
951 | var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort; |
---|
952 | var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames; |
---|
953 | var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort; |
---|
954 | var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames; |
---|
955 | // Check whether a format character is doubled |
---|
956 | var lookAhead = function(match) { |
---|
957 | var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match); |
---|
958 | if (matches) |
---|
959 | iFormat++; |
---|
960 | return matches; |
---|
961 | }; |
---|
962 | // Format a number, with leading zero if necessary |
---|
963 | var formatNumber = function(match, value) { |
---|
964 | return (lookAhead(match) && value < 10 ? '0' : '') + value; |
---|
965 | }; |
---|
966 | // Format a name, short or long as requested |
---|
967 | var formatName = function(match, value, shortNames, longNames) { |
---|
968 | return (lookAhead(match) ? longNames[value] : shortNames[value]); |
---|
969 | }; |
---|
970 | var output = ''; |
---|
971 | var literal = false; |
---|
972 | if (date) |
---|
973 | for (var iFormat = 0; iFormat < format.length; iFormat++) { |
---|
974 | if (literal) |
---|
975 | if (format.charAt(iFormat) == "'" && !lookAhead("'")) |
---|
976 | literal = false; |
---|
977 | else |
---|
978 | output += format.charAt(iFormat); |
---|
979 | else |
---|
980 | switch (format.charAt(iFormat)) { |
---|
981 | case 'd': |
---|
982 | output += formatNumber('d', date.getDate()); |
---|
983 | break; |
---|
984 | case 'D': |
---|
985 | output += formatName('D', date.getDay(), dayNamesShort, dayNames); |
---|
986 | break; |
---|
987 | case 'm': |
---|
988 | output += formatNumber('m', date.getMonth() + 1); |
---|
989 | break; |
---|
990 | case 'M': |
---|
991 | output += formatName('M', date.getMonth(), monthNamesShort, monthNames); |
---|
992 | break; |
---|
993 | case 'y': |
---|
994 | output += (lookAhead('y') ? date.getFullYear() : |
---|
995 | (date.getYear() % 100 < 10 ? '0' : '') + date.getYear() % 100); |
---|
996 | break; |
---|
997 | case '@': |
---|
998 | output += date.getTime(); |
---|
999 | break; |
---|
1000 | case "'": |
---|
1001 | if (lookAhead("'")) |
---|
1002 | output += "'"; |
---|
1003 | else |
---|
1004 | literal = true; |
---|
1005 | break; |
---|
1006 | default: |
---|
1007 | output += format.charAt(iFormat); |
---|
1008 | } |
---|
1009 | } |
---|
1010 | return output; |
---|
1011 | }, |
---|
1012 | |
---|
1013 | /* Extract all possible characters from the date format. */ |
---|
1014 | _possibleChars: function (format) { |
---|
1015 | var chars = ''; |
---|
1016 | var literal = false; |
---|
1017 | for (var iFormat = 0; iFormat < format.length; iFormat++) |
---|
1018 | if (literal) |
---|
1019 | if (format.charAt(iFormat) == "'" && !lookAhead("'")) |
---|
1020 | literal = false; |
---|
1021 | else |
---|
1022 | chars += format.charAt(iFormat); |
---|
1023 | else |
---|
1024 | switch (format.charAt(iFormat)) { |
---|
1025 | case 'd': case 'm': case 'y': case '@': |
---|
1026 | chars += '0123456789'; |
---|
1027 | break; |
---|
1028 | case 'D': case 'M': |
---|
1029 | return null; // Accept anything |
---|
1030 | case "'": |
---|
1031 | if (lookAhead("'")) |
---|
1032 | chars += "'"; |
---|
1033 | else |
---|
1034 | literal = true; |
---|
1035 | break; |
---|
1036 | default: |
---|
1037 | chars += format.charAt(iFormat); |
---|
1038 | } |
---|
1039 | return chars; |
---|
1040 | }, |
---|
1041 | |
---|
1042 | /* Get a setting value, defaulting if necessary. */ |
---|
1043 | _get: function(inst, name) { |
---|
1044 | return inst.settings[name] !== undefined ? |
---|
1045 | inst.settings[name] : this._defaults[name]; |
---|
1046 | }, |
---|
1047 | |
---|
1048 | /* Parse existing date and initialise date picker. */ |
---|
1049 | _setDateFromField: function(inst) { |
---|
1050 | var dateFormat = this._get(inst, 'dateFormat'); |
---|
1051 | var dates = inst.input ? inst.input.val().split(this._get(inst, 'rangeSeparator')) : null; |
---|
1052 | inst.endDay = inst.endMonth = inst.endYear = null; |
---|
1053 | var date = defaultDate = this._getDefaultDate(inst); |
---|
1054 | if (dates.length > 0) { |
---|
1055 | var settings = this._getFormatConfig(inst); |
---|
1056 | if (dates.length > 1) { |
---|
1057 | date = this.parseDate(dateFormat, dates[1], settings) || defaultDate; |
---|
1058 | inst.endDay = date.getDate(); |
---|
1059 | inst.endMonth = date.getMonth(); |
---|
1060 | inst.endYear = date.getFullYear(); |
---|
1061 | } |
---|
1062 | try { |
---|
1063 | date = this.parseDate(dateFormat, dates[0], settings) || defaultDate; |
---|
1064 | } catch (e) { |
---|
1065 | this.log(e); |
---|
1066 | date = defaultDate; |
---|
1067 | } |
---|
1068 | } |
---|
1069 | inst.selectedDay = date.getDate(); |
---|
1070 | inst.drawMonth = inst.selectedMonth = date.getMonth(); |
---|
1071 | inst.drawYear = inst.selectedYear = date.getFullYear(); |
---|
1072 | inst.currentDay = (dates[0] ? date.getDate() : 0); |
---|
1073 | inst.currentMonth = (dates[0] ? date.getMonth() : 0); |
---|
1074 | inst.currentYear = (dates[0] ? date.getFullYear() : 0); |
---|
1075 | this._adjustInstDate(inst); |
---|
1076 | }, |
---|
1077 | |
---|
1078 | /* Retrieve the default date shown on opening. */ |
---|
1079 | _getDefaultDate: function(inst) { |
---|
1080 | var date = this._determineDate(this._get(inst, 'defaultDate'), new Date()); |
---|
1081 | var minDate = this._getMinMaxDate(inst, 'min', true); |
---|
1082 | var maxDate = this._getMinMaxDate(inst, 'max'); |
---|
1083 | date = (minDate && date < minDate ? minDate : date); |
---|
1084 | date = (maxDate && date > maxDate ? maxDate : date); |
---|
1085 | return date; |
---|
1086 | }, |
---|
1087 | |
---|
1088 | /* A date may be specified as an exact value or a relative one. */ |
---|
1089 | _determineDate: function(date, defaultDate) { |
---|
1090 | var offsetNumeric = function(offset) { |
---|
1091 | var date = new Date(); |
---|
1092 | date.setDate(date.getDate() + offset); |
---|
1093 | return date; |
---|
1094 | }; |
---|
1095 | var offsetString = function(offset, getDaysInMonth) { |
---|
1096 | var date = new Date(); |
---|
1097 | var year = date.getFullYear(); |
---|
1098 | var month = date.getMonth(); |
---|
1099 | var day = date.getDate(); |
---|
1100 | var pattern = /([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g; |
---|
1101 | var matches = pattern.exec(offset); |
---|
1102 | while (matches) { |
---|
1103 | switch (matches[2] || 'd') { |
---|
1104 | case 'd' : case 'D' : |
---|
1105 | day += (matches[1] - 0); break; |
---|
1106 | case 'w' : case 'W' : |
---|
1107 | day += (matches[1] * 7); break; |
---|
1108 | case 'm' : case 'M' : |
---|
1109 | month += (matches[1] - 0); |
---|
1110 | day = Math.min(day, getDaysInMonth(year, month)); |
---|
1111 | break; |
---|
1112 | case 'y': case 'Y' : |
---|
1113 | year += (matches[1] - 0); |
---|
1114 | day = Math.min(day, getDaysInMonth(year, month)); |
---|
1115 | break; |
---|
1116 | } |
---|
1117 | matches = pattern.exec(offset); |
---|
1118 | } |
---|
1119 | return new Date(year, month, day); |
---|
1120 | }; |
---|
1121 | date = (date == null ? defaultDate : |
---|
1122 | (typeof date == 'string' ? offsetString(date, this._getDaysInMonth) : |
---|
1123 | (typeof date == 'number' ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : date))); |
---|
1124 | date = (date && date.toString() == 'Invalid Date' ? defaultDate : date); |
---|
1125 | if (date) { |
---|
1126 | date.setHours(0); |
---|
1127 | date.setMinutes(0); |
---|
1128 | date.setSeconds(0); |
---|
1129 | date.setMilliseconds(0); |
---|
1130 | } |
---|
1131 | return this._daylightSavingAdjust(date); |
---|
1132 | }, |
---|
1133 | |
---|
1134 | /* Handle switch to/from daylight saving. |
---|
1135 | Hours may be non-zero on daylight saving cut-over: |
---|
1136 | > 12 when midnight changeover, but then cannot generate |
---|
1137 | midnight datetime, so jump to 1AM, otherwise reset. |
---|
1138 | @param date (Date) the date to check |
---|
1139 | @return (Date) the corrected date */ |
---|
1140 | _daylightSavingAdjust: function(date) { |
---|
1141 | if (!date) return null; |
---|
1142 | date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0); |
---|
1143 | return date; |
---|
1144 | }, |
---|
1145 | |
---|
1146 | /* Set the date(s) directly. */ |
---|
1147 | _setDate: function(inst, date, endDate) { |
---|
1148 | var clear = !(date); |
---|
1149 | date = this._determineDate(date, new Date()); |
---|
1150 | inst.selectedDay = inst.currentDay = date.getDate(); |
---|
1151 | inst.drawMonth = inst.selectedMonth = inst.currentMonth = date.getMonth(); |
---|
1152 | inst.drawYear = inst.selectedYear = inst.currentYear = date.getFullYear(); |
---|
1153 | if (this._get(inst, 'rangeSelect')) { |
---|
1154 | if (endDate) { |
---|
1155 | endDate = this._determineDate(endDate, null); |
---|
1156 | inst.endDay = endDate.getDate(); |
---|
1157 | inst.endMonth = endDate.getMonth(); |
---|
1158 | inst.endYear = endDate.getFullYear(); |
---|
1159 | } else { |
---|
1160 | inst.endDay = inst.currentDay; |
---|
1161 | inst.endMonth = inst.currentMonth; |
---|
1162 | inst.endYear = inst.currentYear; |
---|
1163 | } |
---|
1164 | } |
---|
1165 | this._adjustInstDate(inst); |
---|
1166 | if (inst.input) |
---|
1167 | inst.input.val(clear ? '' : this._formatDate(inst) + |
---|
1168 | (!this._get(inst, 'rangeSelect') ? '' : this._get(inst, 'rangeSeparator') + |
---|
1169 | this._formatDate(inst, inst.endDay, inst.endMonth, inst.endYear))); |
---|
1170 | }, |
---|
1171 | |
---|
1172 | /* Retrieve the date(s) directly. */ |
---|
1173 | _getDate: function(inst) { |
---|
1174 | var startDate = (!inst.currentYear || (inst.input && inst.input.val() == '') ? null : |
---|
1175 | this._daylightSavingAdjust(new Date( |
---|
1176 | inst.currentYear, inst.currentMonth, inst.currentDay))); |
---|
1177 | if (this._get(inst, 'rangeSelect')) { |
---|
1178 | return [inst.rangeStart || startDate, |
---|
1179 | (!inst.endYear ? inst.rangeStart || startDate : |
---|
1180 | this._daylightSavingAdjust(new Date(inst.endYear, inst.endMonth, inst.endDay)))]; |
---|
1181 | } else |
---|
1182 | return startDate; |
---|
1183 | }, |
---|
1184 | |
---|
1185 | /* Generate the HTML for the current state of the date picker. */ |
---|
1186 | _generateDatepicker: function(inst) { |
---|
1187 | var today = new Date(); |
---|
1188 | today = this._daylightSavingAdjust( |
---|
1189 | new Date(today.getFullYear(), today.getMonth(), today.getDate())); // clear time |
---|
1190 | var showStatus = this._get(inst, 'showStatus'); |
---|
1191 | var isRTL = this._get(inst, 'isRTL'); |
---|
1192 | // build the date picker HTML |
---|
1193 | var clear = (this._get(inst, 'mandatory') ? '' : |
---|
1194 | '<div class="ui-datepicker-clear"><a onclick="jQuery.datepicker._clearDate(\'#' + inst.id + '\');"' + |
---|
1195 | (showStatus ? this._addStatus(inst, this._get(inst, 'clearStatus') || ' ') : '') + '>' + |
---|
1196 | this._get(inst, 'clearText') + '</a></div>'); |
---|
1197 | var controls = '<div class="ui-datepicker-control">' + (isRTL ? '' : clear) + |
---|
1198 | '<div class="ui-datepicker-close"><a onclick="jQuery.datepicker._hideDatepicker();"' + |
---|
1199 | (showStatus ? this._addStatus(inst, this._get(inst, 'closeStatus') || ' ') : '') + '>' + |
---|
1200 | this._get(inst, 'closeText') + '</a></div>' + (isRTL ? clear : '') + '</div>'; |
---|
1201 | var prompt = this._get(inst, 'prompt'); |
---|
1202 | var closeAtTop = this._get(inst, 'closeAtTop'); |
---|
1203 | var hideIfNoPrevNext = this._get(inst, 'hideIfNoPrevNext'); |
---|
1204 | var navigationAsDateFormat = this._get(inst, 'navigationAsDateFormat'); |
---|
1205 | var numMonths = this._getNumberOfMonths(inst); |
---|
1206 | var stepMonths = this._get(inst, 'stepMonths'); |
---|
1207 | var isMultiMonth = (numMonths[0] != 1 || numMonths[1] != 1); |
---|
1208 | var currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) : |
---|
1209 | new Date(inst.currentYear, inst.currentMonth, inst.currentDay))); |
---|
1210 | var minDate = this._getMinMaxDate(inst, 'min', true); |
---|
1211 | var maxDate = this._getMinMaxDate(inst, 'max'); |
---|
1212 | var drawMonth = inst.drawMonth; |
---|
1213 | var drawYear = inst.drawYear; |
---|
1214 | if (maxDate) { |
---|
1215 | var maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(), |
---|
1216 | maxDate.getMonth() - numMonths[1] + 1, maxDate.getDate())); |
---|
1217 | maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw); |
---|
1218 | while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) { |
---|
1219 | drawMonth--; |
---|
1220 | if (drawMonth < 0) { |
---|
1221 | drawMonth = 11; |
---|
1222 | drawYear--; |
---|
1223 | } |
---|
1224 | } |
---|
1225 | } |
---|
1226 | // controls and links |
---|
1227 | var prevText = this._get(inst, 'prevText'); |
---|
1228 | prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText, |
---|
1229 | this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)), |
---|
1230 | this._getFormatConfig(inst))); |
---|
1231 | var prev = '<div class="ui-datepicker-prev">' + (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ? |
---|
1232 | '<a onclick="jQuery.datepicker._adjustDate(\'#' + inst.id + '\', -' + stepMonths + ', \'M\');"' + |
---|
1233 | (showStatus ? this._addStatus(inst, this._get(inst, 'prevStatus') || ' ') : '') + '>' + prevText + '</a>' : |
---|
1234 | (hideIfNoPrevNext ? '' : '<label>' + prevText + '</label>')) + '</div>'; |
---|
1235 | var nextText = this._get(inst, 'nextText'); |
---|
1236 | nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText, |
---|
1237 | this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)), |
---|
1238 | this._getFormatConfig(inst))); |
---|
1239 | var next = '<div class="ui-datepicker-next">' + (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ? |
---|
1240 | '<a onclick="jQuery.datepicker._adjustDate(\'#' + inst.id + '\', +' + stepMonths + ', \'M\');"' + |
---|
1241 | (showStatus ? this._addStatus(inst, this._get(inst, 'nextStatus') || ' ') : '') + '>' + nextText + '</a>' : |
---|
1242 | (hideIfNoPrevNext ? '' : '<label>' + nextText + '</label>')) + '</div>'; |
---|
1243 | var currentText = this._get(inst, 'currentText'); |
---|
1244 | currentText = (!navigationAsDateFormat ? currentText: this.formatDate( |
---|
1245 | currentText, today, this._getFormatConfig(inst))); |
---|
1246 | var html = (prompt ? '<div class="' + this._promptClass + '">' + prompt + '</div>' : '') + |
---|
1247 | (closeAtTop && !inst.inline ? controls : '') + |
---|
1248 | '<div class="ui-datepicker-links">' + (isRTL ? next : prev) + |
---|
1249 | (this._isInRange(inst, (this._get(inst, 'gotoCurrent') && inst.currentDay ? |
---|
1250 | currentDate : today)) ? '<div class="ui-datepicker-current">' + |
---|
1251 | '<a onclick="jQuery.datepicker._gotoToday(\'#' + inst.id + '\');"' + |
---|
1252 | (showStatus ? this._addStatus(inst, this._get(inst, 'currentStatus') || ' ') : '') + '>' + |
---|
1253 | currentText + '</a></div>' : '') + (isRTL ? prev : next) + '</div>'; |
---|
1254 | var firstDay = this._get(inst, 'firstDay'); |
---|
1255 | var changeFirstDay = this._get(inst, 'changeFirstDay'); |
---|
1256 | var dayNames = this._get(inst, 'dayNames'); |
---|
1257 | var dayNamesShort = this._get(inst, 'dayNamesShort'); |
---|
1258 | var dayNamesMin = this._get(inst, 'dayNamesMin'); |
---|
1259 | var monthNames = this._get(inst, 'monthNames'); |
---|
1260 | var beforeShowDay = this._get(inst, 'beforeShowDay'); |
---|
1261 | var highlightWeek = this._get(inst, 'highlightWeek'); |
---|
1262 | var showOtherMonths = this._get(inst, 'showOtherMonths'); |
---|
1263 | var showWeeks = this._get(inst, 'showWeeks'); |
---|
1264 | var calculateWeek = this._get(inst, 'calculateWeek') || this.iso8601Week; |
---|
1265 | var status = (showStatus ? this._get(inst, 'dayStatus') || ' ' : ''); |
---|
1266 | var dateStatus = this._get(inst, 'statusForDate') || this.dateStatus; |
---|
1267 | var endDate = inst.endDay ? this._daylightSavingAdjust( |
---|
1268 | new Date(inst.endYear, inst.endMonth, inst.endDay)) : currentDate; |
---|
1269 | for (var row = 0; row < numMonths[0]; row++) |
---|
1270 | for (var col = 0; col < numMonths[1]; col++) { |
---|
1271 | var selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay)); |
---|
1272 | html += '<div class="ui-datepicker-one-month' + (col == 0 ? ' ui-datepicker-new-row' : '') + '">' + |
---|
1273 | this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate, |
---|
1274 | selectedDate, row > 0 || col > 0, showStatus, monthNames) + // draw month headers |
---|
1275 | '<table class="ui-datepicker" cellpadding="0" cellspacing="0"><thead>' + |
---|
1276 | '<tr class="ui-datepicker-title-row">' + |
---|
1277 | (showWeeks ? '<td>' + this._get(inst, 'weekHeader') + '</td>' : ''); |
---|
1278 | for (var dow = 0; dow < 7; dow++) { // days of the week |
---|
1279 | var day = (dow + firstDay) % 7; |
---|
1280 | var dayStatus = (status.indexOf('DD') > -1 ? status.replace(/DD/, dayNames[day]) : |
---|
1281 | status.replace(/D/, dayNamesShort[day])); |
---|
1282 | html += '<td' + ((dow + firstDay + 6) % 7 >= 5 ? ' class="ui-datepicker-week-end-cell"' : '') + '>' + |
---|
1283 | (!changeFirstDay ? '<span' : |
---|
1284 | '<a onclick="jQuery.datepicker._changeFirstDay(\'#' + inst.id + '\', ' + day + ');"') + |
---|
1285 | (showStatus ? this._addStatus(inst, dayStatus) : '') + ' title="' + dayNames[day] + '">' + |
---|
1286 | dayNamesMin[day] + (changeFirstDay ? '</a>' : '</span>') + '</td>'; |
---|
1287 | } |
---|
1288 | html += '</tr></thead><tbody>'; |
---|
1289 | var daysInMonth = this._getDaysInMonth(drawYear, drawMonth); |
---|
1290 | if (drawYear == inst.selectedYear && drawMonth == inst.selectedMonth) |
---|
1291 | inst.selectedDay = Math.min(inst.selectedDay, daysInMonth); |
---|
1292 | var leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7; |
---|
1293 | var numRows = (isMultiMonth ? 6 : Math.ceil((leadDays + daysInMonth) / 7)); // calculate the number of rows to generate |
---|
1294 | var printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays)); |
---|
1295 | for (var dRow = 0; dRow < numRows; dRow++) { // create date picker rows |
---|
1296 | html += '<tr class="ui-datepicker-days-row">' + |
---|
1297 | (showWeeks ? '<td class="ui-datepicker-week-col">' + calculateWeek(printDate) + '</td>' : ''); |
---|
1298 | for (var dow = 0; dow < 7; dow++) { // create date picker days |
---|
1299 | var daySettings = (beforeShowDay ? |
---|
1300 | beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, '']); |
---|
1301 | var otherMonth = (printDate.getMonth() != drawMonth); |
---|
1302 | var unselectable = otherMonth || !daySettings[0] || |
---|
1303 | (minDate && printDate < minDate) || (maxDate && printDate > maxDate); |
---|
1304 | html += '<td class="ui-datepicker-days-cell' + |
---|
1305 | ((dow + firstDay + 6) % 7 >= 5 ? ' ui-datepicker-week-end-cell' : '') + // highlight weekends |
---|
1306 | (otherMonth ? ' ui-datepicker-otherMonth' : '') + // highlight days from other months |
---|
1307 | (printDate.getTime() == selectedDate.getTime() && drawMonth == inst.selectedMonth ? |
---|
1308 | ' ui-datepicker-days-cell-over' : '') + // highlight selected day |
---|
1309 | (unselectable ? ' ' + this._unselectableClass : '') + // highlight unselectable days |
---|
1310 | (otherMonth && !showOtherMonths ? '' : ' ' + daySettings[1] + // highlight custom dates |
---|
1311 | (printDate.getTime() >= currentDate.getTime() && printDate.getTime() <= endDate.getTime() ? // in current range |
---|
1312 | ' ' + this._currentClass : '') + // highlight selected day |
---|
1313 | (printDate.getTime() == today.getTime() ? ' ui-datepicker-today' : '')) + '"' + // highlight today (if different) |
---|
1314 | ((!otherMonth || showOtherMonths) && daySettings[2] ? ' title="' + daySettings[2] + '"' : '') + // cell title |
---|
1315 | (unselectable ? (highlightWeek ? ' onmouseover="jQuery(this).parent().addClass(\'ui-datepicker-week-over\');"' + // highlight selection week |
---|
1316 | ' onmouseout="jQuery(this).parent().removeClass(\'ui-datepicker-week-over\');"' : '') : // unhighlight selection week |
---|
1317 | ' onmouseover="jQuery(this).addClass(\'ui-datepicker-days-cell-over\')' + // highlight selection |
---|
1318 | (highlightWeek ? '.parent().addClass(\'ui-datepicker-week-over\')' : '') + ';' + // highlight selection week |
---|
1319 | (!showStatus || (otherMonth && !showOtherMonths) ? '' : 'jQuery(\'#ui-datepicker-status-' + |
---|
1320 | inst.id + '\').html(\'' + (dateStatus.apply((inst.input ? inst.input[0] : null), |
---|
1321 | [printDate, inst]) || ' ') +'\');') + '"' + |
---|
1322 | ' onmouseout="jQuery(this).removeClass(\'ui-datepicker-days-cell-over\')' + // unhighlight selection |
---|
1323 | (highlightWeek ? '.parent().removeClass(\'ui-datepicker-week-over\')' : '') + ';' + // unhighlight selection week |
---|
1324 | (!showStatus || (otherMonth && !showOtherMonths) ? '' : 'jQuery(\'#ui-datepicker-status-' + |
---|
1325 | inst.id + '\').html(\' \');') + '" onclick="jQuery.datepicker._selectDay(\'#' + |
---|
1326 | inst.id + '\',' + drawMonth + ',' + drawYear + ', this);"') + '>' + // actions |
---|
1327 | (otherMonth ? (showOtherMonths ? printDate.getDate() : ' ') : // display for other months |
---|
1328 | (unselectable ? printDate.getDate() : '<a>' + printDate.getDate() + '</a>')) + '</td>'; // display for this month |
---|
1329 | printDate.setDate(printDate.getDate() + 1); |
---|
1330 | printDate = this._daylightSavingAdjust(printDate); |
---|
1331 | } |
---|
1332 | html += '</tr>'; |
---|
1333 | } |
---|
1334 | drawMonth++; |
---|
1335 | if (drawMonth > 11) { |
---|
1336 | drawMonth = 0; |
---|
1337 | drawYear++; |
---|
1338 | } |
---|
1339 | html += '</tbody></table></div>'; |
---|
1340 | } |
---|
1341 | html += (showStatus ? '<div style="clear: both;"></div><div id="ui-datepicker-status-' + inst.id + |
---|
1342 | '" class="ui-datepicker-status">' + (this._get(inst, 'initStatus') || ' ') + '</div>' : '') + |
---|
1343 | (!closeAtTop && !inst.inline ? controls : '') + |
---|
1344 | '<div style="clear: both;"></div>' + |
---|
1345 | ($.browser.msie && parseInt($.browser.version) < 7 && !inst.inline ? |
---|
1346 | '<iframe src="javascript:false;" class="ui-datepicker-cover"></iframe>' : ''); |
---|
1347 | return html; |
---|
1348 | }, |
---|
1349 | |
---|
1350 | /* Generate the month and year header. */ |
---|
1351 | _generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate, |
---|
1352 | selectedDate, secondary, showStatus, monthNames) { |
---|
1353 | minDate = (inst.rangeStart && minDate && selectedDate < minDate ? selectedDate : minDate); |
---|
1354 | var html = '<div class="ui-datepicker-header">'; |
---|
1355 | // month selection |
---|
1356 | if (secondary || !this._get(inst, 'changeMonth')) |
---|
1357 | html += monthNames[drawMonth] + ' '; |
---|
1358 | else { |
---|
1359 | var inMinYear = (minDate && minDate.getFullYear() == drawYear); |
---|
1360 | var inMaxYear = (maxDate && maxDate.getFullYear() == drawYear); |
---|
1361 | html += '<select class="ui-datepicker-new-month" ' + |
---|
1362 | 'onchange="jQuery.datepicker._selectMonthYear(\'#' + inst.id + '\', this, \'M\');" ' + |
---|
1363 | 'onclick="jQuery.datepicker._clickMonthYear(\'#' + inst.id + '\');"' + |
---|
1364 | (showStatus ? this._addStatus(inst, this._get(inst, 'monthStatus') || ' ') : '') + '>'; |
---|
1365 | for (var month = 0; month < 12; month++) { |
---|
1366 | if ((!inMinYear || month >= minDate.getMonth()) && |
---|
1367 | (!inMaxYear || month <= maxDate.getMonth())) |
---|
1368 | html += '<option value="' + month + '"' + |
---|
1369 | (month == drawMonth ? ' selected="selected"' : '') + |
---|
1370 | '>' + monthNames[month] + '</option>'; |
---|
1371 | } |
---|
1372 | html += '</select>'; |
---|
1373 | } |
---|
1374 | // year selection |
---|
1375 | if (secondary || !this._get(inst, 'changeYear')) |
---|
1376 | html += drawYear; |
---|
1377 | else { |
---|
1378 | // determine range of years to display |
---|
1379 | var years = this._get(inst, 'yearRange').split(':'); |
---|
1380 | var year = 0; |
---|
1381 | var endYear = 0; |
---|
1382 | if (years.length != 2) { |
---|
1383 | year = drawYear - 10; |
---|
1384 | endYear = drawYear + 10; |
---|
1385 | } else if (years[0].charAt(0) == '+' || years[0].charAt(0) == '-') { |
---|
1386 | year = endYear = new Date().getFullYear(); |
---|
1387 | year += parseInt(years[0], 10); |
---|
1388 | endYear += parseInt(years[1], 10); |
---|
1389 | } else { |
---|
1390 | year = parseInt(years[0], 10); |
---|
1391 | endYear = parseInt(years[1], 10); |
---|
1392 | } |
---|
1393 | year = (minDate ? Math.max(year, minDate.getFullYear()) : year); |
---|
1394 | endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear); |
---|
1395 | html += '<select class="ui-datepicker-new-year" ' + |
---|
1396 | 'onchange="jQuery.datepicker._selectMonthYear(\'#' + inst.id + '\', this, \'Y\');" ' + |
---|
1397 | 'onclick="jQuery.datepicker._clickMonthYear(\'#' + inst.id + '\');"' + |
---|
1398 | (showStatus ? this._addStatus(inst, this._get(inst, 'yearStatus') || ' ') : '') + '>'; |
---|
1399 | for (; year <= endYear; year++) { |
---|
1400 | html += '<option value="' + year + '"' + |
---|
1401 | (year == drawYear ? ' selected="selected"' : '') + |
---|
1402 | '>' + year + '</option>'; |
---|
1403 | } |
---|
1404 | html += '</select>'; |
---|
1405 | } |
---|
1406 | html += '</div>'; // Close datepicker_header |
---|
1407 | return html; |
---|
1408 | }, |
---|
1409 | |
---|
1410 | /* Provide code to set and clear the status panel. */ |
---|
1411 | _addStatus: function(inst, text) { |
---|
1412 | return ' onmouseover="jQuery(\'#ui-datepicker-status-' + inst.id + '\').html(\'' + text + '\');" ' + |
---|
1413 | 'onmouseout="jQuery(\'#ui-datepicker-status-' + inst.id + '\').html(\' \');"'; |
---|
1414 | }, |
---|
1415 | |
---|
1416 | /* Adjust one of the date sub-fields. */ |
---|
1417 | _adjustInstDate: function(inst, offset, period) { |
---|
1418 | var year = inst.drawYear + (period == 'Y' ? offset : 0); |
---|
1419 | var month = inst.drawMonth + (period == 'M' ? offset : 0); |
---|
1420 | var day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + |
---|
1421 | (period == 'D' ? offset : 0); |
---|
1422 | var date = this._daylightSavingAdjust(new Date(year, month, day)); |
---|
1423 | // ensure it is within the bounds set |
---|
1424 | var minDate = this._getMinMaxDate(inst, 'min', true); |
---|
1425 | var maxDate = this._getMinMaxDate(inst, 'max'); |
---|
1426 | date = (minDate && date < minDate ? minDate : date); |
---|
1427 | date = (maxDate && date > maxDate ? maxDate : date); |
---|
1428 | inst.selectedDay = date.getDate(); |
---|
1429 | inst.drawMonth = inst.selectedMonth = date.getMonth(); |
---|
1430 | inst.drawYear = inst.selectedYear = date.getFullYear(); |
---|
1431 | if (period == 'M' || period == 'Y') |
---|
1432 | this._notifyChange(inst); |
---|
1433 | }, |
---|
1434 | |
---|
1435 | /* Notify change of month/year. */ |
---|
1436 | _notifyChange: function(inst) { |
---|
1437 | var onChange = this._get(inst, 'onChangeMonthYear'); |
---|
1438 | if (onChange) |
---|
1439 | onChange.apply((inst.input ? inst.input[0] : null), |
---|
1440 | [new Date(inst.selectedYear, inst.selectedMonth, 1), inst]); |
---|
1441 | }, |
---|
1442 | |
---|
1443 | /* Determine the number of months to show. */ |
---|
1444 | _getNumberOfMonths: function(inst) { |
---|
1445 | var numMonths = this._get(inst, 'numberOfMonths'); |
---|
1446 | return (numMonths == null ? [1, 1] : (typeof numMonths == 'number' ? [1, numMonths] : numMonths)); |
---|
1447 | }, |
---|
1448 | |
---|
1449 | /* Determine the current maximum date - ensure no time components are set - may be overridden for a range. */ |
---|
1450 | _getMinMaxDate: function(inst, minMax, checkRange) { |
---|
1451 | var date = this._determineDate(this._get(inst, minMax + 'Date'), null); |
---|
1452 | return (!checkRange || !inst.rangeStart ? date : |
---|
1453 | (!date || inst.rangeStart > date ? inst.rangeStart : date)); |
---|
1454 | }, |
---|
1455 | |
---|
1456 | /* Find the number of days in a given month. */ |
---|
1457 | _getDaysInMonth: function(year, month) { |
---|
1458 | return 32 - new Date(year, month, 32).getDate(); |
---|
1459 | }, |
---|
1460 | |
---|
1461 | /* Find the day of the week of the first of a month. */ |
---|
1462 | _getFirstDayOfMonth: function(year, month) { |
---|
1463 | return new Date(year, month, 1).getDay(); |
---|
1464 | }, |
---|
1465 | |
---|
1466 | /* Determines if we should allow a "next/prev" month display change. */ |
---|
1467 | _canAdjustMonth: function(inst, offset, curYear, curMonth) { |
---|
1468 | var numMonths = this._getNumberOfMonths(inst); |
---|
1469 | var date = this._daylightSavingAdjust(new Date( |
---|
1470 | curYear, curMonth + (offset < 0 ? offset : numMonths[1]), 1)); |
---|
1471 | if (offset < 0) |
---|
1472 | date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth())); |
---|
1473 | return this._isInRange(inst, date); |
---|
1474 | }, |
---|
1475 | |
---|
1476 | /* Is the given date in the accepted range? */ |
---|
1477 | _isInRange: function(inst, date) { |
---|
1478 | // during range selection, use minimum of selected date and range start |
---|
1479 | var newMinDate = (!inst.rangeStart ? null : this._daylightSavingAdjust( |
---|
1480 | new Date(inst.selectedYear, inst.selectedMonth, inst.selectedDay))); |
---|
1481 | newMinDate = (newMinDate && inst.rangeStart < newMinDate ? inst.rangeStart : newMinDate); |
---|
1482 | var minDate = newMinDate || this._getMinMaxDate(inst, 'min'); |
---|
1483 | var maxDate = this._getMinMaxDate(inst, 'max'); |
---|
1484 | return ((!minDate || date >= minDate) && (!maxDate || date <= maxDate)); |
---|
1485 | }, |
---|
1486 | |
---|
1487 | /* Provide the configuration settings for formatting/parsing. */ |
---|
1488 | _getFormatConfig: function(inst) { |
---|
1489 | var shortYearCutoff = this._get(inst, 'shortYearCutoff'); |
---|
1490 | shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff : |
---|
1491 | new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); |
---|
1492 | return {shortYearCutoff: shortYearCutoff, |
---|
1493 | dayNamesShort: this._get(inst, 'dayNamesShort'), dayNames: this._get(inst, 'dayNames'), |
---|
1494 | monthNamesShort: this._get(inst, 'monthNamesShort'), monthNames: this._get(inst, 'monthNames')}; |
---|
1495 | }, |
---|
1496 | |
---|
1497 | /* Format the given date for display. */ |
---|
1498 | _formatDate: function(inst, day, month, year) { |
---|
1499 | if (!day) { |
---|
1500 | inst.currentDay = inst.selectedDay; |
---|
1501 | inst.currentMonth = inst.selectedMonth; |
---|
1502 | inst.currentYear = inst.selectedYear; |
---|
1503 | } |
---|
1504 | var date = (day ? (typeof day == 'object' ? day : |
---|
1505 | this._daylightSavingAdjust(new Date(year, month, day))) : |
---|
1506 | this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay))); |
---|
1507 | return this.formatDate(this._get(inst, 'dateFormat'), date, this._getFormatConfig(inst)); |
---|
1508 | } |
---|
1509 | }); |
---|
1510 | |
---|
1511 | /* jQuery extend now ignores nulls! */ |
---|
1512 | function extendRemove(target, props) { |
---|
1513 | $.extend(target, props); |
---|
1514 | for (var name in props) |
---|
1515 | if (props[name] == null || props[name] == undefined) |
---|
1516 | target[name] = props[name]; |
---|
1517 | return target; |
---|
1518 | }; |
---|
1519 | |
---|
1520 | /* Determine whether an object is an array. */ |
---|
1521 | function isArray(a) { |
---|
1522 | return (a && (($.browser.safari && typeof a == 'object' && a.length) || |
---|
1523 | (a.constructor && a.constructor.toString().match(/\Array\(\)/)))); |
---|
1524 | }; |
---|
1525 | |
---|
1526 | /* Invoke the datepicker functionality. |
---|
1527 | @param options string - a command, optionally followed by additional parameters or |
---|
1528 | Object - settings for attaching new datepicker functionality |
---|
1529 | @return jQuery object */ |
---|
1530 | $.fn.datepicker = function(options){ |
---|
1531 | var otherArgs = Array.prototype.slice.call(arguments, 1); |
---|
1532 | if (typeof options == 'string' && (options == 'isDisabled' || options == 'getDate')) |
---|
1533 | return $.datepicker['_' + options + 'Datepicker']. |
---|
1534 | apply($.datepicker, [this[0]].concat(otherArgs)); |
---|
1535 | return this.each(function() { |
---|
1536 | typeof options == 'string' ? |
---|
1537 | $.datepicker['_' + options + 'Datepicker']. |
---|
1538 | apply($.datepicker, [this].concat(otherArgs)) : |
---|
1539 | $.datepicker._attachDatepicker(this, options); |
---|
1540 | }); |
---|
1541 | }; |
---|
1542 | |
---|
1543 | $.datepicker = new Datepicker(); // singleton instance |
---|
1544 | |
---|
1545 | /* Initialise the date picker. */ |
---|
1546 | $(document).ready(function() { |
---|
1547 | $(document.body).append($.datepicker.dpDiv). |
---|
1548 | mousedown($.datepicker._checkExternalClick); |
---|
1549 | }); |
---|
1550 | |
---|
1551 | })(jQuery); |
---|