Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

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