jquery_datepick 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +58 -0
  5. data/Rakefile +1 -0
  6. data/jquery_datepick.gemspec +20 -0
  7. data/lib/jquery_datepick/datepick_helper.rb +13 -0
  8. data/lib/jquery_datepick/form_helper.rb +61 -0
  9. data/lib/jquery_datepick/version.rb +3 -0
  10. data/lib/jquery_datepick.rb +16 -0
  11. data/vendor/assets/images/calendar-blue.gif +0 -0
  12. data/vendor/assets/images/calendar-green.gif +0 -0
  13. data/vendor/assets/images/calendar.gif +0 -0
  14. data/vendor/assets/javascripts/jquery.datepick-af.js +29 -0
  15. data/vendor/assets/javascripts/jquery.datepick-am.js +29 -0
  16. data/vendor/assets/javascripts/jquery.datepick-ar-DZ.js +28 -0
  17. data/vendor/assets/javascripts/jquery.datepick-ar-EG.js +29 -0
  18. data/vendor/assets/javascripts/jquery.datepick-ar.js +30 -0
  19. data/vendor/assets/javascripts/jquery.datepick-az.js +29 -0
  20. data/vendor/assets/javascripts/jquery.datepick-bg.js +29 -0
  21. data/vendor/assets/javascripts/jquery.datepick-bs.js +29 -0
  22. data/vendor/assets/javascripts/jquery.datepick-ca.js +29 -0
  23. data/vendor/assets/javascripts/jquery.datepick-cs.js +29 -0
  24. data/vendor/assets/javascripts/jquery.datepick-da.js +29 -0
  25. data/vendor/assets/javascripts/jquery.datepick-de-CH.js +29 -0
  26. data/vendor/assets/javascripts/jquery.datepick-de.js +29 -0
  27. data/vendor/assets/javascripts/jquery.datepick-el.js +29 -0
  28. data/vendor/assets/javascripts/jquery.datepick-en-AU.js +29 -0
  29. data/vendor/assets/javascripts/jquery.datepick-en-GB.js +29 -0
  30. data/vendor/assets/javascripts/jquery.datepick-en-NZ.js +29 -0
  31. data/vendor/assets/javascripts/jquery.datepick-eo.js +29 -0
  32. data/vendor/assets/javascripts/jquery.datepick-es-AR.js +29 -0
  33. data/vendor/assets/javascripts/jquery.datepick-es-PE.js +29 -0
  34. data/vendor/assets/javascripts/jquery.datepick-es.js +29 -0
  35. data/vendor/assets/javascripts/jquery.datepick-et.js +29 -0
  36. data/vendor/assets/javascripts/jquery.datepick-eu.js +29 -0
  37. data/vendor/assets/javascripts/jquery.datepick-fa.js +29 -0
  38. data/vendor/assets/javascripts/jquery.datepick-fi.js +29 -0
  39. data/vendor/assets/javascripts/jquery.datepick-fo.js +29 -0
  40. data/vendor/assets/javascripts/jquery.datepick-fr-CH.js +29 -0
  41. data/vendor/assets/javascripts/jquery.datepick-fr.js +29 -0
  42. data/vendor/assets/javascripts/jquery.datepick-gl.js +29 -0
  43. data/vendor/assets/javascripts/jquery.datepick-gu.js +29 -0
  44. data/vendor/assets/javascripts/jquery.datepick-he.js +29 -0
  45. data/vendor/assets/javascripts/jquery.datepick-hi-IN.js +27 -0
  46. data/vendor/assets/javascripts/jquery.datepick-hi.js +27 -0
  47. data/vendor/assets/javascripts/jquery.datepick-hr.js +29 -0
  48. data/vendor/assets/javascripts/jquery.datepick-hu.js +29 -0
  49. data/vendor/assets/javascripts/jquery.datepick-hy.js +29 -0
  50. data/vendor/assets/javascripts/jquery.datepick-id.js +29 -0
  51. data/vendor/assets/javascripts/jquery.datepick-is.js +29 -0
  52. data/vendor/assets/javascripts/jquery.datepick-it.js +29 -0
  53. data/vendor/assets/javascripts/jquery.datepick-ja.js +31 -0
  54. data/vendor/assets/javascripts/jquery.datepick-ka.js +29 -0
  55. data/vendor/assets/javascripts/jquery.datepick-km.js +29 -0
  56. data/vendor/assets/javascripts/jquery.datepick-ko.js +31 -0
  57. data/vendor/assets/javascripts/jquery.datepick-lt.js +29 -0
  58. data/vendor/assets/javascripts/jquery.datepick-lv.js +29 -0
  59. data/vendor/assets/javascripts/jquery.datepick-me-ME.js +29 -0
  60. data/vendor/assets/javascripts/jquery.datepick-me.js +29 -0
  61. data/vendor/assets/javascripts/jquery.datepick-mk.js +31 -0
  62. data/vendor/assets/javascripts/jquery.datepick-ml.js +29 -0
  63. data/vendor/assets/javascripts/jquery.datepick-ms.js +29 -0
  64. data/vendor/assets/javascripts/jquery.datepick-mt.js +29 -0
  65. data/vendor/assets/javascripts/jquery.datepick-nl-BE.js +29 -0
  66. data/vendor/assets/javascripts/jquery.datepick-nl.js +29 -0
  67. data/vendor/assets/javascripts/jquery.datepick-no.js +29 -0
  68. data/vendor/assets/javascripts/jquery.datepick-pl.js +29 -0
  69. data/vendor/assets/javascripts/jquery.datepick-pt-BR.js +29 -0
  70. data/vendor/assets/javascripts/jquery.datepick-pt.js +29 -0
  71. data/vendor/assets/javascripts/jquery.datepick-rm.js +29 -0
  72. data/vendor/assets/javascripts/jquery.datepick-ro.js +29 -0
  73. data/vendor/assets/javascripts/jquery.datepick-ru.js +29 -0
  74. data/vendor/assets/javascripts/jquery.datepick-sk.js +29 -0
  75. data/vendor/assets/javascripts/jquery.datepick-sl.js +30 -0
  76. data/vendor/assets/javascripts/jquery.datepick-sq.js +29 -0
  77. data/vendor/assets/javascripts/jquery.datepick-sr-SR.js +29 -0
  78. data/vendor/assets/javascripts/jquery.datepick-sr.js +29 -0
  79. data/vendor/assets/javascripts/jquery.datepick-sv.js +29 -0
  80. data/vendor/assets/javascripts/jquery.datepick-ta.js +29 -0
  81. data/vendor/assets/javascripts/jquery.datepick-th.js +29 -0
  82. data/vendor/assets/javascripts/jquery.datepick-tr.js +29 -0
  83. data/vendor/assets/javascripts/jquery.datepick-uk.js +29 -0
  84. data/vendor/assets/javascripts/jquery.datepick-ur.js +30 -0
  85. data/vendor/assets/javascripts/jquery.datepick-vi.js +29 -0
  86. data/vendor/assets/javascripts/jquery.datepick-zh-CN.js +31 -0
  87. data/vendor/assets/javascripts/jquery.datepick-zh-HK.js +31 -0
  88. data/vendor/assets/javascripts/jquery.datepick-zh-TW.js +31 -0
  89. data/vendor/assets/javascripts/jquery.datepick.ext.js +264 -0
  90. data/vendor/assets/javascripts/jquery.datepick.ext.min.js +7 -0
  91. data/vendor/assets/javascripts/jquery.datepick.js +2056 -0
  92. data/vendor/assets/javascripts/jquery.datepick.lang.js +2185 -0
  93. data/vendor/assets/javascripts/jquery.datepick.lang.min.js +3 -0
  94. data/vendor/assets/javascripts/jquery.datepick.min.js +7 -0
  95. data/vendor/assets/javascripts/jquery.datepick.validation.js +230 -0
  96. data/vendor/assets/javascripts/jquery.datepick.validation.min.js +8 -0
  97. data/vendor/assets/stylesheets/flora.datepick.css +208 -0
  98. data/vendor/assets/stylesheets/humanity.datepick.css +197 -0
  99. data/vendor/assets/stylesheets/jquery.datepick.css +216 -0
  100. data/vendor/assets/stylesheets/redmond.datepick.css +199 -0
  101. data/vendor/assets/stylesheets/smoothness.datepick.css +200 -0
  102. data/vendor/assets/stylesheets/ui-black-tie.datepick.css +18 -0
  103. data/vendor/assets/stylesheets/ui-blitzer.datepick.css +18 -0
  104. data/vendor/assets/stylesheets/ui-cupertino.datepick.css +18 -0
  105. data/vendor/assets/stylesheets/ui-dark-hive.datepick.css +18 -0
  106. data/vendor/assets/stylesheets/ui-dot-luv.datepick.css +18 -0
  107. data/vendor/assets/stylesheets/ui-eggplant.datepick.css +18 -0
  108. data/vendor/assets/stylesheets/ui-excite-bike.datepick.css +18 -0
  109. data/vendor/assets/stylesheets/ui-flick.datepick.css +18 -0
  110. data/vendor/assets/stylesheets/ui-hot-sneaks.datepick.css +18 -0
  111. data/vendor/assets/stylesheets/ui-humanity.datepick.css +18 -0
  112. data/vendor/assets/stylesheets/ui-le-frog.datepick.css +18 -0
  113. data/vendor/assets/stylesheets/ui-mint-choc.datepick.css +18 -0
  114. data/vendor/assets/stylesheets/ui-overcast.datepick.css +18 -0
  115. data/vendor/assets/stylesheets/ui-pepper-grinder.datepick.css +18 -0
  116. data/vendor/assets/stylesheets/ui-redmond.datepick.css +18 -0
  117. data/vendor/assets/stylesheets/ui-smoothness.datepick.css +18 -0
  118. data/vendor/assets/stylesheets/ui-south-street.datepick.css +18 -0
  119. data/vendor/assets/stylesheets/ui-start.datepick.css +18 -0
  120. data/vendor/assets/stylesheets/ui-sunny.datepick.css +18 -0
  121. data/vendor/assets/stylesheets/ui-swanky-purse.datepick.css +18 -0
  122. data/vendor/assets/stylesheets/ui-trontastic.datepick.css +18 -0
  123. data/vendor/assets/stylesheets/ui-ui-darkness.datepick.css +18 -0
  124. data/vendor/assets/stylesheets/ui-ui-lightness.datepick.css +18 -0
  125. data/vendor/assets/stylesheets/ui-vader.datepick.css +18 -0
  126. data/vendor/assets/stylesheets/ui.datepick.css +118 -0
  127. metadata +194 -0
@@ -0,0 +1,2056 @@
1
+ /* http://keith-wood.name/datepick.html
2
+ Date picker for jQuery v4.1.0.
3
+ Written by Keith Wood (kbwood{at}iinet.com.au) February 2010.
4
+ Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
5
+ MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
6
+ Please attribute the author if you use it. */
7
+
8
+ (function($) { // Hide scope, no $ conflict
9
+
10
+ /* Datepicker manager. */
11
+ function Datepicker() {
12
+ this._defaults = {
13
+ pickerClass: '', // CSS class to add to this instance of the datepicker
14
+ showOnFocus: true, // True for popup on focus, false for not
15
+ showTrigger: null, // Element to be cloned for a trigger, null for none
16
+ showAnim: 'show', // Name of jQuery animation for popup, '' for no animation
17
+ showOptions: {}, // Options for enhanced animations
18
+ showSpeed: 'normal', // Duration of display/closure
19
+ popupContainer: null, // The element to which a popup calendar is added, null for body
20
+ alignment: 'bottom', // Alignment of popup - with nominated corner of input:
21
+ // 'top' or 'bottom' aligns depending on language direction,
22
+ // 'topLeft', 'topRight', 'bottomLeft', 'bottomRight'
23
+ fixedWeeks: false, // True to always show 6 weeks, false to only show as many as are needed
24
+ firstDay: 0, // First day of the week, 0 = Sunday, 1 = Monday, ...
25
+ calculateWeek: this.iso8601Week, // Calculate week of the year from a date, null for ISO8601
26
+ monthsToShow: 1, // How many months to show, cols or [rows, cols]
27
+ monthsOffset: 0, // How many months to offset the primary month by;
28
+ // may be a function that takes the date and returns the offset
29
+ monthsToStep: 1, // How many months to move when prev/next clicked
30
+ monthsToJump: 12, // How many months to move when large prev/next clicked
31
+ useMouseWheel: true, // True to use mousewheel if available, false to never use it
32
+ changeMonth: true, // True to change month/year via drop-down, false for navigation only
33
+ yearRange: 'c-10:c+10', // Range of years to show in drop-down: 'any' for direct text entry
34
+ // or 'start:end', where start/end are '+-nn' for relative to today
35
+ // or 'c+-nn' for relative to the currently selected date
36
+ // or 'nnnn' for an absolute year
37
+ shortYearCutoff: '+10', // Cutoff for two-digit year in the current century
38
+ showOtherMonths: false, // True to show dates from other months, false to not show them
39
+ selectOtherMonths: false, // True to allow selection of dates from other months too
40
+ defaultDate: null, // Date to show if no other selected
41
+ selectDefaultDate: false, // True to pre-select the default date if no other is chosen
42
+ minDate: null, // The minimum selectable date
43
+ maxDate: null, // The maximum selectable date
44
+ dateFormat: 'mm/dd/yyyy', // Format for dates
45
+ autoSize: false, // True to size the input field according to the date format
46
+ rangeSelect: false, // Allows for selecting a date range on one date picker
47
+ rangeSeparator: ' - ', // Text between two dates in a range
48
+ multiSelect: 0, // Maximum number of selectable dates, zero for single select
49
+ multiSeparator: ',', // Text between multiple dates
50
+ onDate: null, // Callback as a date is added to the datepicker
51
+ onShow: null, // Callback just before a datepicker is shown
52
+ onChangeMonthYear: null, // Callback when a new month/year is selected
53
+ onSelect: null, // Callback when a date is selected
54
+ onClose: null, // Callback when a datepicker is closed
55
+ altField: null, // Alternate field to update in synch with the datepicker
56
+ altFormat: null, // Date format for alternate field, defaults to dateFormat
57
+ constrainInput: true, // True to constrain typed input to dateFormat allowed characters
58
+ commandsAsDateFormat: false, // True to apply formatDate to the command texts
59
+ commands: this.commands // Command actions that may be added to a layout by name
60
+ };
61
+ this.regional = [];
62
+ this.regional[''] = { // US/English
63
+ monthNames: ['January', 'February', 'March', 'April', 'May', 'June',
64
+ 'July', 'August', 'September', 'October', 'November', 'December'],
65
+ monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
66
+ dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
67
+ dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
68
+ dayNamesMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
69
+ dateFormat: 'mm/dd/yyyy', // See options on formatDate
70
+ firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
71
+ renderer: this.defaultRenderer, // The rendering templates
72
+ prevText: '<Prev', // Text for the previous month command
73
+ prevStatus: 'Show the previous month', // Status text for the previous month command
74
+ prevJumpText: '<<', // Text for the previous year command
75
+ prevJumpStatus: 'Show the previous year', // Status text for the previous year command
76
+ nextText: 'Next>', // Text for the next month command
77
+ nextStatus: 'Show the next month', // Status text for the next month command
78
+ nextJumpText: '>>', // Text for the next year command
79
+ nextJumpStatus: 'Show the next year', // Status text for the next year command
80
+ currentText: 'Current', // Text for the current month command
81
+ currentStatus: 'Show the current month', // Status text for the current month command
82
+ todayText: 'Today', // Text for the today's month command
83
+ todayStatus: 'Show today\'s month', // Status text for the today's month command
84
+ clearText: 'Clear', // Text for the clear command
85
+ clearStatus: 'Clear all the dates', // Status text for the clear command
86
+ closeText: 'Close', // Text for the close command
87
+ closeStatus: 'Close the datepicker', // Status text for the close command
88
+ yearStatus: 'Change the year', // Status text for year selection
89
+ monthStatus: 'Change the month', // Status text for month selection
90
+ weekText: 'Wk', // Text for week of the year column header
91
+ weekStatus: 'Week of the year', // Status text for week of the year column header
92
+ dayStatus: 'Select DD, M d, yyyy', // Status text for selectable days
93
+ defaultStatus: 'Select a date', // Status text shown by default
94
+ isRTL: false // True if language is right-to-left
95
+ };
96
+ $.extend(this._defaults, this.regional['']);
97
+ this._disabled = [];
98
+ }
99
+
100
+ $.extend(Datepicker.prototype, {
101
+ /* Class name added to elements to indicate already configured with datepicker. */
102
+ markerClassName: 'hasDatepick',
103
+ /* Name of the data property for instance settings. */
104
+ propertyName: 'datepick',
105
+
106
+ _popupClass: 'datepick-popup', // Marker for popup division
107
+ _triggerClass: 'datepick-trigger', // Marker for trigger element
108
+ _disableClass: 'datepick-disable', // Marker for disabled element
109
+ _monthYearClass: 'datepick-month-year', // Marker for month/year inputs
110
+ _curMonthClass: 'datepick-month-', // Marker for current month/year
111
+ _anyYearClass: 'datepick-any-year', // Marker for year direct input
112
+ _curDoWClass: 'datepick-dow-', // Marker for day of week
113
+
114
+ commands: { // Command actions that may be added to a layout by name
115
+ // name: { // The command name, use '{button:name}' or '{link:name}' in layouts
116
+ // text: '', // The field in the regional settings for the displayed text
117
+ // status: '', // The field in the regional settings for the status text
118
+ // // The keystroke to trigger the action
119
+ // keystroke: {keyCode: nn, ctrlKey: boolean, altKey: boolean, shiftKey: boolean},
120
+ // enabled: fn, // The function that indicates the command is enabled
121
+ // date: fn, // The function to get the date associated with this action
122
+ // action: fn} // The function that implements the action
123
+ prev: {text: 'prevText', status: 'prevStatus', // Previous month
124
+ keystroke: {keyCode: 33}, // Page up
125
+ enabled: function(inst) {
126
+ var minDate = inst.curMinDate();
127
+ return (!minDate || plugin.add(plugin.day(
128
+ plugin._applyMonthsOffset(plugin.add(plugin.newDate(inst.drawDate),
129
+ 1 - inst.options.monthsToStep, 'm'), inst), 1), -1, 'd').
130
+ getTime() >= minDate.getTime()); },
131
+ date: function(inst) {
132
+ return plugin.day(plugin._applyMonthsOffset(plugin.add(
133
+ plugin.newDate(inst.drawDate), -inst.options.monthsToStep, 'm'), inst), 1); },
134
+ action: function(inst) {
135
+ plugin._changeMonthPlugin(this, -inst.options.monthsToStep); }
136
+ },
137
+ prevJump: {text: 'prevJumpText', status: 'prevJumpStatus', // Previous year
138
+ keystroke: {keyCode: 33, ctrlKey: true}, // Ctrl + Page up
139
+ enabled: function(inst) {
140
+ var minDate = inst.curMinDate();
141
+ return (!minDate || plugin.add(plugin.day(
142
+ plugin._applyMonthsOffset(plugin.add(plugin.newDate(inst.drawDate),
143
+ 1 - inst.options.monthsToJump, 'm'), inst), 1), -1, 'd').
144
+ getTime() >= minDate.getTime()); },
145
+ date: function(inst) {
146
+ return plugin.day(plugin._applyMonthsOffset(plugin.add(
147
+ plugin.newDate(inst.drawDate), -inst.options.monthsToJump, 'm'), inst), 1); },
148
+ action: function(inst) {
149
+ plugin._changeMonthPlugin(this, -inst.options.monthsToJump); }
150
+ },
151
+ next: {text: 'nextText', status: 'nextStatus', // Next month
152
+ keystroke: {keyCode: 34}, // Page down
153
+ enabled: function(inst) {
154
+ var maxDate = inst.get('maxDate');
155
+ return (!maxDate || plugin.day(plugin._applyMonthsOffset(plugin.add(
156
+ plugin.newDate(inst.drawDate), inst.options.monthsToStep, 'm'), inst), 1).
157
+ getTime() <= maxDate.getTime()); },
158
+ date: function(inst) {
159
+ return plugin.day(plugin._applyMonthsOffset(plugin.add(
160
+ plugin.newDate(inst.drawDate), inst.options.monthsToStep, 'm'), inst), 1); },
161
+ action: function(inst) {
162
+ plugin._changeMonthPlugin(this, inst.options.monthsToStep); }
163
+ },
164
+ nextJump: {text: 'nextJumpText', status: 'nextJumpStatus', // Next year
165
+ keystroke: {keyCode: 34, ctrlKey: true}, // Ctrl + Page down
166
+ enabled: function(inst) {
167
+ var maxDate = inst.get('maxDate');
168
+ return (!maxDate || plugin.day(plugin._applyMonthsOffset(plugin.add(
169
+ plugin.newDate(inst.drawDate), inst.options.monthsToJump, 'm'), inst), 1).
170
+ getTime() <= maxDate.getTime()); },
171
+ date: function(inst) {
172
+ return plugin.day(plugin._applyMonthsOffset(plugin.add(
173
+ plugin.newDate(inst.drawDate), inst.options.monthsToJump, 'm'), inst), 1); },
174
+ action: function(inst) {
175
+ plugin._changeMonthPlugin(this, inst.options.monthsToJump); }
176
+ },
177
+ current: {text: 'currentText', status: 'currentStatus', // Current month
178
+ keystroke: {keyCode: 36, ctrlKey: true}, // Ctrl + Home
179
+ enabled: function(inst) {
180
+ var minDate = inst.curMinDate();
181
+ var maxDate = inst.get('maxDate');
182
+ var curDate = inst.selectedDates[0] || plugin.today();
183
+ return (!minDate || curDate.getTime() >= minDate.getTime()) &&
184
+ (!maxDate || curDate.getTime() <= maxDate.getTime()); },
185
+ date: function(inst) {
186
+ return inst.selectedDates[0] || plugin.today(); },
187
+ action: function(inst) {
188
+ var curDate = inst.selectedDates[0] || plugin.today();
189
+ plugin._showMonthPlugin(this, curDate.getFullYear(), curDate.getMonth() + 1); }
190
+ },
191
+ today: {text: 'todayText', status: 'todayStatus', // Today's month
192
+ keystroke: {keyCode: 36, ctrlKey: true}, // Ctrl + Home
193
+ enabled: function(inst) {
194
+ var minDate = inst.curMinDate();
195
+ var maxDate = inst.get('maxDate');
196
+ return (!minDate || plugin.today().getTime() >= minDate.getTime()) &&
197
+ (!maxDate || plugin.today().getTime() <= maxDate.getTime()); },
198
+ date: function(inst) { return plugin.today(); },
199
+ action: function(inst) { plugin._showMonthPlugin(this); }
200
+ },
201
+ clear: {text: 'clearText', status: 'clearStatus', // Clear the datepicker
202
+ keystroke: {keyCode: 35, ctrlKey: true}, // Ctrl + End
203
+ enabled: function(inst) { return true; },
204
+ date: function(inst) { return null; },
205
+ action: function(inst) { plugin._clearPlugin(this); }
206
+ },
207
+ close: {text: 'closeText', status: 'closeStatus', // Close the datepicker
208
+ keystroke: {keyCode: 27}, // Escape
209
+ enabled: function(inst) { return true; },
210
+ date: function(inst) { return null; },
211
+ action: function(inst) { plugin._hidePlugin(this); }
212
+ },
213
+ prevWeek: {text: 'prevWeekText', status: 'prevWeekStatus', // Previous week
214
+ keystroke: {keyCode: 38, ctrlKey: true}, // Ctrl + Up
215
+ enabled: function(inst) {
216
+ var minDate = inst.curMinDate();
217
+ return (!minDate || plugin.add(plugin.newDate(inst.drawDate), -7, 'd').
218
+ getTime() >= minDate.getTime()); },
219
+ date: function(inst) { return plugin.add(plugin.newDate(inst.drawDate), -7, 'd'); },
220
+ action: function(inst) { plugin._changeDayPlugin(this, -7); }
221
+ },
222
+ prevDay: {text: 'prevDayText', status: 'prevDayStatus', // Previous day
223
+ keystroke: {keyCode: 37, ctrlKey: true}, // Ctrl + Left
224
+ enabled: function(inst) {
225
+ var minDate = inst.curMinDate();
226
+ return (!minDate || plugin.add(plugin.newDate(inst.drawDate), -1, 'd').
227
+ getTime() >= minDate.getTime()); },
228
+ date: function(inst) { return plugin.add(plugin.newDate(inst.drawDate), -1, 'd'); },
229
+ action: function(inst) { plugin._changeDayPlugin(this, -1); }
230
+ },
231
+ nextDay: {text: 'nextDayText', status: 'nextDayStatus', // Next day
232
+ keystroke: {keyCode: 39, ctrlKey: true}, // Ctrl + Right
233
+ enabled: function(inst) {
234
+ var maxDate = inst.get('maxDate');
235
+ return (!maxDate || plugin.add(plugin.newDate(inst.drawDate), 1, 'd').
236
+ getTime() <= maxDate.getTime()); },
237
+ date: function(inst) { return plugin.add(plugin.newDate(inst.drawDate), 1, 'd'); },
238
+ action: function(inst) { plugin._changeDayPlugin(this, 1); }
239
+ },
240
+ nextWeek: {text: 'nextWeekText', status: 'nextWeekStatus', // Next week
241
+ keystroke: {keyCode: 40, ctrlKey: true}, // Ctrl + Down
242
+ enabled: function(inst) {
243
+ var maxDate = inst.get('maxDate');
244
+ return (!maxDate || plugin.add(plugin.newDate(inst.drawDate), 7, 'd').
245
+ getTime() <= maxDate.getTime()); },
246
+ date: function(inst) { return plugin.add(plugin.newDate(inst.drawDate), 7, 'd'); },
247
+ action: function(inst) { plugin._changeDayPlugin(this, 7); }
248
+ }
249
+ },
250
+
251
+ /* Default template for generating a datepicker. */
252
+ defaultRenderer: {
253
+ // Anywhere: '{l10n:name}' to insert localised value for name,
254
+ // '{link:name}' to insert a link trigger for command name,
255
+ // '{button:name}' to insert a button trigger for command name,
256
+ // '{popup:start}...{popup:end}' to mark a section for inclusion in a popup datepicker only,
257
+ // '{inline:start}...{inline:end}' to mark a section for inclusion in an inline datepicker only
258
+ // Overall structure: '{months}' to insert calendar months
259
+ picker: '<div class="datepick">' +
260
+ '<div class="datepick-nav">{link:prev}{link:today}{link:next}</div>{months}' +
261
+ '{popup:start}<div class="datepick-ctrl">{link:clear}{link:close}</div>{popup:end}' +
262
+ '<div class="datepick-clear-fix"></div></div>',
263
+ // One row of months: '{months}' to insert calendar months
264
+ monthRow: '<div class="datepick-month-row">{months}</div>',
265
+ // A single month: '{monthHeader:dateFormat}' to insert the month header -
266
+ // dateFormat is optional and defaults to 'MM yyyy',
267
+ // '{weekHeader}' to insert a week header, '{weeks}' to insert the month's weeks
268
+ month: '<div class="datepick-month"><div class="datepick-month-header">{monthHeader}</div>' +
269
+ '<table><thead>{weekHeader}</thead><tbody>{weeks}</tbody></table></div>',
270
+ // A week header: '{days}' to insert individual day names
271
+ weekHeader: '<tr>{days}</tr>',
272
+ // Individual day header: '{day}' to insert day name
273
+ dayHeader: '<th>{day}</th>',
274
+ // One week of the month: '{days}' to insert the week's days, '{weekOfYear}' to insert week of year
275
+ week: '<tr>{days}</tr>',
276
+ // An individual day: '{day}' to insert day value
277
+ day: '<td>{day}</td>',
278
+ // jQuery selector, relative to picker, for a single month
279
+ monthSelector: '.datepick-month',
280
+ // jQuery selector, relative to picker, for individual days
281
+ daySelector: 'td',
282
+ // Class for right-to-left (RTL) languages
283
+ rtlClass: 'datepick-rtl',
284
+ // Class for multi-month datepickers
285
+ multiClass: 'datepick-multi',
286
+ // Class for selectable dates
287
+ defaultClass: '',
288
+ // Class for currently selected dates
289
+ selectedClass: 'datepick-selected',
290
+ // Class for highlighted dates
291
+ highlightedClass: 'datepick-highlight',
292
+ // Class for today
293
+ todayClass: 'datepick-today',
294
+ // Class for days from other months
295
+ otherMonthClass: 'datepick-other-month',
296
+ // Class for days on weekends
297
+ weekendClass: 'datepick-weekend',
298
+ // Class prefix for commands
299
+ commandClass: 'datepick-cmd',
300
+ // Extra class(es) for commands that are buttons
301
+ commandButtonClass: '',
302
+ // Extra class(es) for commands that are links
303
+ commandLinkClass: '',
304
+ // Class for disabled commands
305
+ disabledClass: 'datepick-disabled'
306
+ },
307
+
308
+ /* Override the default settings for all datepicker instances.
309
+ @param options (object) the new settings to use as defaults
310
+ @return (Datepicker) this object */
311
+ setDefaults: function(options) {
312
+ $.extend(this._defaults, options || {});
313
+ return this;
314
+ },
315
+
316
+ _ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) +
317
+ Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000),
318
+ _msPerDay: 24 * 60 * 60 * 1000,
319
+
320
+ ATOM: 'yyyy-mm-dd', // RFC 3339/ISO 8601
321
+ COOKIE: 'D, dd M yyyy',
322
+ FULL: 'DD, MM d, yyyy',
323
+ ISO_8601: 'yyyy-mm-dd',
324
+ JULIAN: 'J',
325
+ RFC_822: 'D, d M yy',
326
+ RFC_850: 'DD, dd-M-yy',
327
+ RFC_1036: 'D, d M yy',
328
+ RFC_1123: 'D, d M yyyy',
329
+ RFC_2822: 'D, d M yyyy',
330
+ RSS: 'D, d M yy', // RFC 822
331
+ TICKS: '!',
332
+ TIMESTAMP: '@',
333
+ W3C: 'yyyy-mm-dd', // ISO 8601
334
+
335
+ /* Format a date object into a string value.
336
+ The format can be combinations of the following:
337
+ d - day of month (no leading zero)
338
+ dd - day of month (two digit)
339
+ o - day of year (no leading zeros)
340
+ oo - day of year (three digit)
341
+ D - day name short
342
+ DD - day name long
343
+ w - week of year (no leading zero)
344
+ ww - week of year (two digit)
345
+ m - month of year (no leading zero)
346
+ mm - month of year (two digit)
347
+ M - month name short
348
+ MM - month name long
349
+ yy - year (two digit)
350
+ yyyy - year (four digit)
351
+ @ - Unix timestamp (s since 01/01/1970)
352
+ ! - Windows ticks (100ns since 01/01/0001)
353
+ '...' - literal text
354
+ '' - single quote
355
+ @param format (string) the desired format of the date (optional, default datepicker format)
356
+ @param date (Date) the date value to format
357
+ @param settings (object) attributes include:
358
+ dayNamesShort (string[]) abbreviated names of the days from Sunday (optional)
359
+ dayNames (string[]) names of the days from Sunday (optional)
360
+ monthNamesShort (string[]) abbreviated names of the months (optional)
361
+ monthNames (string[]) names of the months (optional)
362
+ calculateWeek (function) function that determines week of the year (optional)
363
+ @return (string) the date in the above format */
364
+ formatDate: function(format, date, settings) {
365
+ if (typeof format != 'string') {
366
+ settings = date;
367
+ date = format;
368
+ format = '';
369
+ }
370
+ if (!date) {
371
+ return '';
372
+ }
373
+ format = format || this._defaults.dateFormat;
374
+ settings = settings || {};
375
+ var dayNamesShort = settings.dayNamesShort || this._defaults.dayNamesShort;
376
+ var dayNames = settings.dayNames || this._defaults.dayNames;
377
+ var monthNamesShort = settings.monthNamesShort || this._defaults.monthNamesShort;
378
+ var monthNames = settings.monthNames || this._defaults.monthNames;
379
+ var calculateWeek = settings.calculateWeek || this._defaults.calculateWeek;
380
+ // Check whether a format character is doubled
381
+ var doubled = function(match, step) {
382
+ var matches = 1;
383
+ while (iFormat + matches < format.length && format.charAt(iFormat + matches) == match) {
384
+ matches++;
385
+ }
386
+ iFormat += matches - 1;
387
+ return Math.floor(matches / (step || 1)) > 1;
388
+ };
389
+ // Format a number, with leading zeroes if necessary
390
+ var formatNumber = function(match, value, len, step) {
391
+ var num = '' + value;
392
+ if (doubled(match, step)) {
393
+ while (num.length < len) {
394
+ num = '0' + num;
395
+ }
396
+ }
397
+ return num;
398
+ };
399
+ // Format a name, short or long as requested
400
+ var formatName = function(match, value, shortNames, longNames) {
401
+ return (doubled(match) ? longNames[value] : shortNames[value]);
402
+ };
403
+ var output = '';
404
+ var literal = false;
405
+ for (var iFormat = 0; iFormat < format.length; iFormat++) {
406
+ if (literal) {
407
+ if (format.charAt(iFormat) == "'" && !doubled("'")) {
408
+ literal = false;
409
+ }
410
+ else {
411
+ output += format.charAt(iFormat);
412
+ }
413
+ }
414
+ else {
415
+ switch (format.charAt(iFormat)) {
416
+ case 'd': output += formatNumber('d', date.getDate(), 2); break;
417
+ case 'D': output += formatName('D', date.getDay(),
418
+ dayNamesShort, dayNames); break;
419
+ case 'o': output += formatNumber('o', this.dayOfYear(date), 3); break;
420
+ case 'w': output += formatNumber('w', calculateWeek(date), 2); break;
421
+ case 'm': output += formatNumber('m', date.getMonth() + 1, 2); break;
422
+ case 'M': output += formatName('M', date.getMonth(),
423
+ monthNamesShort, monthNames); break;
424
+ case 'y':
425
+ output += (doubled('y', 2) ? date.getFullYear() :
426
+ (date.getFullYear() % 100 < 10 ? '0' : '') + date.getFullYear() % 100);
427
+ break;
428
+ case '@': output += Math.floor(date.getTime() / 1000); break;
429
+ case '!': output += date.getTime() * 10000 + this._ticksTo1970; break;
430
+ case "'":
431
+ if (doubled("'")) {
432
+ output += "'";
433
+ }
434
+ else {
435
+ literal = true;
436
+ }
437
+ break;
438
+ default:
439
+ output += format.charAt(iFormat);
440
+ }
441
+ }
442
+ }
443
+ return output;
444
+ },
445
+
446
+ /* Parse a string value into a date object.
447
+ See formatDate for the possible formats, plus:
448
+ * - ignore rest of string
449
+ @param format (string) the expected format of the date ('' for default datepicker format)
450
+ @param value (string) the date in the above format
451
+ @param settings (object) attributes include:
452
+ shortYearCutoff (number) the cutoff year for determining the century (optional)
453
+ dayNamesShort (string[]) abbreviated names of the days from Sunday (optional)
454
+ dayNames (string[]) names of the days from Sunday (optional)
455
+ monthNamesShort (string[]) abbreviated names of the months (optional)
456
+ monthNames (string[]) names of the months (optional)
457
+ @return (Date) the extracted date value or null if value is blank
458
+ @throws errors if the format and/or value are missing,
459
+ if the value doesn't match the format,
460
+ or if the date is invalid */
461
+ parseDate: function(format, value, settings) {
462
+ if (value == null) {
463
+ throw 'Invalid arguments';
464
+ }
465
+ value = (typeof value == 'object' ? value.toString() : value + '');
466
+ if (value == '') {
467
+ return null;
468
+ }
469
+ format = format || this._defaults.dateFormat;
470
+ settings = settings || {};
471
+ var shortYearCutoff = settings.shortYearCutoff || this._defaults.shortYearCutoff;
472
+ shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff :
473
+ this.today().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
474
+ var dayNamesShort = settings.dayNamesShort || this._defaults.dayNamesShort;
475
+ var dayNames = settings.dayNames || this._defaults.dayNames;
476
+ var monthNamesShort = settings.monthNamesShort || this._defaults.monthNamesShort;
477
+ var monthNames = settings.monthNames || this._defaults.monthNames;
478
+ var year = -1;
479
+ var month = -1;
480
+ var day = -1;
481
+ var doy = -1;
482
+ var shortYear = false;
483
+ var literal = false;
484
+ // Check whether a format character is doubled
485
+ var doubled = function(match, step) {
486
+ var matches = 1;
487
+ while (iFormat + matches < format.length && format.charAt(iFormat + matches) == match) {
488
+ matches++;
489
+ }
490
+ iFormat += matches - 1;
491
+ return Math.floor(matches / (step || 1)) > 1;
492
+ };
493
+ // Extract a number from the string value
494
+ var getNumber = function(match, step) {
495
+ var isDoubled = doubled(match, step);
496
+ var size = [2, 3, isDoubled ? 4 : 2, 11, 20]['oy@!'.indexOf(match) + 1];
497
+ var digits = new RegExp('^-?\\d{1,' + size + '}');
498
+ var num = value.substring(iValue).match(digits);
499
+ if (!num) {
500
+ throw 'Missing number at position {0}'.replace(/\{0\}/, iValue);
501
+ }
502
+ iValue += num[0].length;
503
+ return parseInt(num[0], 10);
504
+ };
505
+ // Extract a name from the string value and convert to an index
506
+ var getName = function(match, shortNames, longNames, step) {
507
+ var names = (doubled(match, step) ? longNames : shortNames);
508
+ for (var i = 0; i < names.length; i++) {
509
+ if (value.substr(iValue, names[i].length).toLowerCase() == names[i].toLowerCase()) {
510
+ iValue += names[i].length;
511
+ return i + 1;
512
+ }
513
+ }
514
+ throw 'Unknown name at position {0}'.replace(/\{0\}/, iValue);
515
+ };
516
+ // Confirm that a literal character matches the string value
517
+ var checkLiteral = function() {
518
+ if (value.charAt(iValue) != format.charAt(iFormat)) {
519
+ throw 'Unexpected literal at position {0}'.replace(/\{0\}/, iValue);
520
+ }
521
+ iValue++;
522
+ };
523
+ var iValue = 0;
524
+ for (var iFormat = 0; iFormat < format.length; iFormat++) {
525
+ if (literal) {
526
+ if (format.charAt(iFormat) == "'" && !doubled("'")) {
527
+ literal = false;
528
+ }
529
+ else {
530
+ checkLiteral();
531
+ }
532
+ }
533
+ else {
534
+ switch (format.charAt(iFormat)) {
535
+ case 'd': day = getNumber('d'); break;
536
+ case 'D': getName('D', dayNamesShort, dayNames); break;
537
+ case 'o': doy = getNumber('o'); break;
538
+ case 'w': getNumber('w'); break;
539
+ case 'm': month = getNumber('m'); break;
540
+ case 'M': month = getName('M', monthNamesShort, monthNames); break;
541
+ case 'y':
542
+ var iSave = iFormat;
543
+ shortYear = !doubled('y', 2);
544
+ iFormat = iSave;
545
+ year = getNumber('y', 2);
546
+ break;
547
+ case '@':
548
+ var date = this._normaliseDate(new Date(getNumber('@') * 1000));
549
+ year = date.getFullYear();
550
+ month = date.getMonth() + 1;
551
+ day = date.getDate();
552
+ break;
553
+ case '!':
554
+ var date = this._normaliseDate(
555
+ new Date((getNumber('!') - this._ticksTo1970) / 10000));
556
+ year = date.getFullYear();
557
+ month = date.getMonth() + 1;
558
+ day = date.getDate();
559
+ break;
560
+ case '*': iValue = value.length; break;
561
+ case "'":
562
+ if (doubled("'")) {
563
+ checkLiteral();
564
+ }
565
+ else {
566
+ literal = true;
567
+ }
568
+ break;
569
+ default: checkLiteral();
570
+ }
571
+ }
572
+ }
573
+ if (iValue < value.length) {
574
+ throw 'Additional text found at end';
575
+ }
576
+ if (year == -1) {
577
+ year = this.today().getFullYear();
578
+ }
579
+ else if (year < 100 && shortYear) {
580
+ year += (shortYearCutoff == -1 ? 1900 : this.today().getFullYear() -
581
+ this.today().getFullYear() % 100 - (year <= shortYearCutoff ? 0 : 100));
582
+ }
583
+ if (doy > -1) {
584
+ month = 1;
585
+ day = doy;
586
+ for (var dim = this.daysInMonth(year, month); day > dim;
587
+ dim = this.daysInMonth(year, month)) {
588
+ month++;
589
+ day -= dim;
590
+ }
591
+ }
592
+ var date = this.newDate(year, month, day);
593
+ if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != day) {
594
+ throw 'Invalid date';
595
+ }
596
+ return date;
597
+ },
598
+
599
+ /* A date may be specified as an exact value or a relative one.
600
+ @param dateSpec (Date or number or string) the date as an object or string
601
+ in the given format or an offset - numeric days from today,
602
+ or string amounts and periods, e.g. '+1m +2w'
603
+ @param defaultDate (Date) the date to use if no other supplied, may be null
604
+ @param currentDate (Date) the current date as a possible basis for relative dates,
605
+ if null today is used (optional)
606
+ @param dateFormat (string) the expected date format - see formatDate above (optional)
607
+ @param settings (object) attributes include:
608
+ shortYearCutoff (number) the cutoff year for determining the century (optional)
609
+ dayNamesShort (string[7]) abbreviated names of the days from Sunday (optional)
610
+ dayNames (string[7]) names of the days from Sunday (optional)
611
+ monthNamesShort (string[12]) abbreviated names of the months (optional)
612
+ monthNames (string[12]) names of the months (optional)
613
+ @return (Date) the decoded date */
614
+ determineDate: function(dateSpec, defaultDate, currentDate, dateFormat, settings) {
615
+ if (currentDate && typeof currentDate != 'object') {
616
+ settings = dateFormat;
617
+ dateFormat = currentDate;
618
+ currentDate = null;
619
+ }
620
+ if (typeof dateFormat != 'string') {
621
+ settings = dateFormat;
622
+ dateFormat = '';
623
+ }
624
+ var offsetString = function(offset) {
625
+ try {
626
+ return plugin.parseDate(dateFormat, offset, settings);
627
+ }
628
+ catch (e) {
629
+ // Ignore
630
+ }
631
+ offset = offset.toLowerCase();
632
+ var date = (offset.match(/^c/) && currentDate ? plugin.newDate(currentDate) : null) ||
633
+ plugin.today();
634
+ var pattern = /([+-]?[0-9]+)\s*(d|w|m|y)?/g;
635
+ var matches = null;
636
+ while (matches = pattern.exec(offset)) {
637
+ date = plugin.add(date, parseInt(matches[1], 10), matches[2] || 'd');
638
+ }
639
+ return date;
640
+ };
641
+ defaultDate = (defaultDate ? plugin.newDate(defaultDate) : null);
642
+ dateSpec = (dateSpec == null ? defaultDate :
643
+ (typeof dateSpec == 'string' ? offsetString(dateSpec) : (typeof dateSpec == 'number' ?
644
+ (isNaN(dateSpec) || dateSpec == Infinity || dateSpec == -Infinity ? defaultDate :
645
+ plugin.add(plugin.today(), dateSpec, 'd')) : plugin.newDate(dateSpec))));
646
+ return dateSpec;
647
+ },
648
+
649
+ /* Find the number of days in a given month.
650
+ @param year (Date) the date to get days for or
651
+ (number) the full year
652
+ @param month (number) the month (1 to 12)
653
+ @return (number) the number of days in this month */
654
+ daysInMonth: function(year, month) {
655
+ month = (year.getFullYear ? year.getMonth() + 1 : month);
656
+ year = (year.getFullYear ? year.getFullYear() : year);
657
+ return this.newDate(year, month + 1, 0).getDate();
658
+ },
659
+
660
+ /* Calculate the day of the year for a date.
661
+ @param year (Date) the date to get the day-of-year for or
662
+ (number) the full year
663
+ @param month (number) the month (1-12)
664
+ @param day (number) the day
665
+ @return (number) the day of the year */
666
+ dayOfYear: function(year, month, day) {
667
+ var date = (year.getFullYear ? year : this.newDate(year, month, day));
668
+ var newYear = this.newDate(date.getFullYear(), 1, 1);
669
+ return Math.floor((date.getTime() - newYear.getTime()) / this._msPerDay) + 1;
670
+ },
671
+
672
+ /* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
673
+ @param year (Date) the date to get the week for or
674
+ (number) the full year
675
+ @param month (number) the month (1-12)
676
+ @param day (number) the day
677
+ @return (number) the number of the week within the year that contains this date */
678
+ iso8601Week: function(year, month, day) {
679
+ var checkDate = (year.getFullYear ?
680
+ new Date(year.getTime()) : this.newDate(year, month, day));
681
+ // Find Thursday of this week starting on Monday
682
+ checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));
683
+ var time = checkDate.getTime();
684
+ checkDate.setMonth(0, 1); // Compare with Jan 1
685
+ return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
686
+ },
687
+
688
+ /* Return today's date.
689
+ @return (Date) today */
690
+ today: function() {
691
+ return this._normaliseDate(new Date());
692
+ },
693
+
694
+ /* Return a new date.
695
+ @param year (Date) the date to clone or
696
+ (number) the year
697
+ @param month (number) the month (1-12)
698
+ @param day (number) the day
699
+ @return (Date) the date */
700
+ newDate: function(year, month, day) {
701
+ return (!year ? null : (year.getFullYear ? this._normaliseDate(new Date(year.getTime())) :
702
+ new Date(year, month - 1, day, 12)));
703
+ },
704
+
705
+ /* Standardise a date into a common format - time portion is 12 noon.
706
+ @param date (Date) the date to standardise
707
+ @return (Date) the normalised date */
708
+ _normaliseDate: function(date) {
709
+ if (date) {
710
+ date.setHours(12, 0, 0, 0);
711
+ }
712
+ return date;
713
+ },
714
+
715
+ /* Set the year for a date.
716
+ @param date (Date) the original date
717
+ @param year (number) the new year
718
+ @return the updated date */
719
+ year: function(date, year) {
720
+ date.setFullYear(year);
721
+ return this._normaliseDate(date);
722
+ },
723
+
724
+ /* Set the month for a date.
725
+ @param date (Date) the original date
726
+ @param month (number) the new month (1-12)
727
+ @return the updated date */
728
+ month: function(date, month) {
729
+ date.setMonth(month - 1);
730
+ return this._normaliseDate(date);
731
+ },
732
+
733
+ /* Set the day for a date.
734
+ @param date (Date) the original date
735
+ @param day (number) the new day of the month
736
+ @return the updated date */
737
+ day: function(date, day) {
738
+ date.setDate(day);
739
+ return this._normaliseDate(date);
740
+ },
741
+
742
+ /* Add a number of periods to a date.
743
+ @param date (Date) the original date
744
+ @param amount (number) the number of periods
745
+ @param period (string) the type of period d/w/m/y
746
+ @return the updated date */
747
+ add: function(date, amount, period) {
748
+ if (period == 'd' || period == 'w') {
749
+ this._normaliseDate(date);
750
+ date.setDate(date.getDate() + amount * (period == 'w' ? 7 : 1));
751
+ }
752
+ else {
753
+ var year = date.getFullYear() + (period == 'y' ? amount : 0);
754
+ var month = date.getMonth() + (period == 'm' ? amount : 0);
755
+ date.setTime(plugin.newDate(year, month + 1,
756
+ Math.min(date.getDate(), this.daysInMonth(year, month + 1))).getTime());
757
+ }
758
+ return date;
759
+ },
760
+
761
+ /* Apply the months offset value to a date.
762
+ @param date (Date) the original date
763
+ @param inst (object) the current instance settings
764
+ @return (Date) the updated date */
765
+ _applyMonthsOffset: function(date, inst) {
766
+ var monthsOffset = inst.options.monthsOffset;
767
+ if ($.isFunction(monthsOffset)) {
768
+ monthsOffset = monthsOffset.apply(inst.target[0], [date]);
769
+ }
770
+ return plugin.add(date, -monthsOffset, 'm');
771
+ },
772
+
773
+ /* Attach the datepicker functionality to an input field.
774
+ @param target (element) the control to affect
775
+ @param options (object) the custom options for this instance */
776
+ _attachPlugin: function(target, options) {
777
+ target = $(target);
778
+ if (target.hasClass(this.markerClassName)) {
779
+ return;
780
+ }
781
+ var inlineSettings = ($.fn.metadata ? target.metadata() : {});
782
+ var inst = {options: $.extend({}, this._defaults, inlineSettings, options),
783
+ target: target, selectedDates: [], drawDate: null, pickingRange: false,
784
+ inline: ($.inArray(target[0].nodeName.toLowerCase(), ['div', 'span']) > -1),
785
+ get: function(name) { // Get a setting value, computing if necessary
786
+ if ($.inArray(name, ['defaultDate', 'minDate', 'maxDate']) > -1) { // Decode date settings
787
+ return plugin.determineDate(this.options[name], null,
788
+ this.selectedDates[0], this.options.dateFormat, inst.getConfig());
789
+ }
790
+ return this.options[name];
791
+ },
792
+ curMinDate: function() {
793
+ return (this.pickingRange ? this.selectedDates[0] : this.get('minDate'));
794
+ },
795
+ getConfig: function() {
796
+ return {dayNamesShort: this.options.dayNamesShort, dayNames: this.options.dayNames,
797
+ monthNamesShort: this.options.monthNamesShort, monthNames: this.options.monthNames,
798
+ calculateWeek: this.options.calculateWeek,
799
+ shortYearCutoff: this.options.shortYearCutoff};
800
+ }
801
+ };
802
+ target.addClass(this.markerClassName).data(this.propertyName, inst);
803
+ if (inst.inline) {
804
+ inst.drawDate = plugin._checkMinMax(plugin.newDate(inst.selectedDates[0] ||
805
+ inst.get('defaultDate') || plugin.today()), inst);
806
+ inst.prevDate = plugin.newDate(inst.drawDate);
807
+ this._update(target[0]);
808
+ if ($.fn.mousewheel) {
809
+ target.mousewheel(this._doMouseWheel);
810
+ }
811
+ }
812
+ else {
813
+ this._attachments(target, inst);
814
+ target.bind('keydown.' + this.propertyName, this._keyDown).
815
+ bind('keypress.' + this.propertyName, this._keyPress).
816
+ bind('keyup.' + this.propertyName, this._keyUp);
817
+ if (target.attr('disabled')) {
818
+ this._disablePlugin(target[0]);
819
+ }
820
+ }
821
+ },
822
+
823
+ /* Retrieve or reconfigure the settings for a control.
824
+ @param target (element) the control to affect
825
+ @param options (object) the new options for this instance or
826
+ (string) an individual property name
827
+ @param value (any) the individual property value (omit if options
828
+ is an object or to retrieve the value of a setting)
829
+ @return (any) if retrieving a value */
830
+ _optionPlugin: function(target, options, value) {
831
+ target = $(target);
832
+ var inst = target.data(this.propertyName);
833
+ if (!options || (typeof options == 'string' && value == null)) { // Get option
834
+ var name = options;
835
+ options = (inst || {}).options;
836
+ return (options && name ? options[name] : options);
837
+ }
838
+
839
+ if (!target.hasClass(this.markerClassName)) {
840
+ return;
841
+ }
842
+ options = options || {};
843
+ if (typeof options == 'string') {
844
+ var name = options;
845
+ options = {};
846
+ options[name] = value;
847
+ }
848
+ if (options.calendar && options.calendar != inst.options.calendar) {
849
+ var discardDate = function(name) {
850
+ return (typeof inst.options[name] == 'object' ? null : inst.options[name]);
851
+ };
852
+ options = $.extend({defaultDate: discardDate('defaultDate'),
853
+ minDate: discardDate('minDate'), maxDate: discardDate('maxDate')}, options);
854
+ inst.selectedDates = [];
855
+ inst.drawDate = null;
856
+ }
857
+ var dates = inst.selectedDates;
858
+ $.extend(inst.options, options);
859
+ this._setDatePlugin(target[0], dates, null, false, true);
860
+ inst.pickingRange = false;
861
+ inst.drawDate = plugin.newDate(this._checkMinMax(
862
+ (inst.options.defaultDate ? inst.get('defaultDate') : inst.drawDate) ||
863
+ inst.get('defaultDate') || plugin.today(), inst));
864
+ if (!inst.inline) {
865
+ this._attachments(target, inst);
866
+ }
867
+ if (inst.inline || inst.div) {
868
+ this._update(target[0]);
869
+ }
870
+ },
871
+
872
+ /* Attach events and trigger, if necessary.
873
+ @param target (jQuery) the control to affect
874
+ @param inst (object) the current instance settings */
875
+ _attachments: function(target, inst) {
876
+ target.unbind('focus.' + this.propertyName);
877
+ if (inst.options.showOnFocus) {
878
+ target.bind('focus.' + this.propertyName, this._showPlugin);
879
+ }
880
+ if (inst.trigger) {
881
+ inst.trigger.remove();
882
+ }
883
+ var trigger = inst.options.showTrigger;
884
+ inst.trigger = (!trigger ? $([]) :
885
+ $(trigger).clone().removeAttr('id').addClass(this._triggerClass)
886
+ [inst.options.isRTL ? 'insertBefore' : 'insertAfter'](target).
887
+ click(function() {
888
+ if (!plugin._isDisabledPlugin(target[0])) {
889
+ plugin[plugin.curInst == inst ? '_hidePlugin' : '_showPlugin'](target[0]);
890
+ }
891
+ }));
892
+ this._autoSize(target, inst);
893
+ var dates = this._extractDates(inst, target.val());
894
+ if (dates) {
895
+ this._setDatePlugin(target[0], dates, null, true);
896
+ }
897
+ var defaultDate = inst.get('defaultDate');
898
+ if (inst.options.selectDefaultDate && defaultDate && inst.selectedDates.length == 0) {
899
+ this._setDatePlugin(target[0], plugin.newDate(defaultDate || plugin.today()));
900
+ }
901
+ },
902
+
903
+ /* Apply the maximum length for the date format.
904
+ @param inst (object) the current instance settings */
905
+ _autoSize: function(target, inst) {
906
+ if (inst.options.autoSize && !inst.inline) {
907
+ var date = plugin.newDate(2009, 10, 20); // Ensure double digits
908
+ var dateFormat = inst.options.dateFormat;
909
+ if (dateFormat.match(/[DM]/)) {
910
+ var findMax = function(names) {
911
+ var max = 0;
912
+ var maxI = 0;
913
+ for (var i = 0; i < names.length; i++) {
914
+ if (names[i].length > max) {
915
+ max = names[i].length;
916
+ maxI = i;
917
+ }
918
+ }
919
+ return maxI;
920
+ };
921
+ date.setMonth(findMax(inst.options[dateFormat.match(/MM/) ? // Longest month
922
+ 'monthNames' : 'monthNamesShort']));
923
+ date.setDate(findMax(inst.options[dateFormat.match(/DD/) ? // Longest day
924
+ 'dayNames' : 'dayNamesShort']) + 20 - date.getDay());
925
+ }
926
+ inst.target.attr('size', plugin.formatDate(dateFormat, date, inst.getConfig()).length);
927
+ }
928
+ },
929
+
930
+ /* Remove the datepicker functionality from a control.
931
+ @param target (element) the control to affect */
932
+ _destroyPlugin: function(target) {
933
+ target = $(target);
934
+ if (!target.hasClass(this.markerClassName)) {
935
+ return;
936
+ }
937
+ var inst = target.data(this.propertyName);
938
+ if (inst.trigger) {
939
+ inst.trigger.remove();
940
+ }
941
+ target.removeClass(this.markerClassName).removeData(this.propertyName).
942
+ empty().unbind('.' + this.propertyName);
943
+ if (inst.inline && $.fn.mousewheel) {
944
+ target.unmousewheel();
945
+ }
946
+ if (!inst.inline && inst.options.autoSize) {
947
+ target.removeAttr('size');
948
+ }
949
+ },
950
+
951
+ /* Apply multiple event functions.
952
+ Usage, for example: onShow: multipleEvents(fn1, fn2, ...)
953
+ @param fns (function...) the functions to apply */
954
+ multipleEvents: function(fns) {
955
+ var funcs = arguments;
956
+ return function(args) {
957
+ for (var i = 0; i < funcs.length; i++) {
958
+ funcs[i].apply(this, arguments);
959
+ }
960
+ };
961
+ },
962
+
963
+ /* Enable the control.
964
+ @param target (element) the control to affect */
965
+ _enablePlugin: function(target) {
966
+ target = $(target);
967
+ if (!target.hasClass(this.markerClassName)) {
968
+ return;
969
+ }
970
+ var inst = target.data(this.propertyName);
971
+ if (inst.inline) {
972
+ target.children('.' + this._disableClass).remove().end().
973
+ find('button,select').removeAttr('disabled').end().
974
+ find('a').attr('href', 'javascript:void(0)');
975
+ }
976
+ else {
977
+ target.prop('disabled', false);
978
+ inst.trigger.filter('button.' + this._triggerClass).
979
+ removeAttr('disabled').end().
980
+ filter('img.' + this._triggerClass).
981
+ css({opacity: '1.0', cursor: ''});
982
+ }
983
+ this._disabled = $.map(this._disabled,
984
+ function(value) { return (value == target[0] ? null : value); }); // Delete entry
985
+ },
986
+
987
+ /* Disable the control.
988
+ @param target (element) the control to affect */
989
+ _disablePlugin: function(target) {
990
+ target = $(target);
991
+ if (!target.hasClass(this.markerClassName)) {
992
+ return;
993
+ }
994
+ var inst = target.data(this.propertyName);
995
+ if (inst.inline) {
996
+ var inline = target.children(':last');
997
+ var offset = inline.offset();
998
+ var relOffset = {left: 0, top: 0};
999
+ inline.parents().each(function() {
1000
+ if ($(this).css('position') == 'relative') {
1001
+ relOffset = $(this).offset();
1002
+ return false;
1003
+ }
1004
+ });
1005
+ var zIndex = target.css('zIndex');
1006
+ zIndex = (zIndex == 'auto' ? 0 : parseInt(zIndex, 10)) + 1;
1007
+ target.prepend('<div class="' + this._disableClass + '" style="' +
1008
+ 'width: ' + inline.outerWidth() + 'px; height: ' + inline.outerHeight() +
1009
+ 'px; left: ' + (offset.left - relOffset.left) + 'px; top: ' +
1010
+ (offset.top - relOffset.top) + 'px; z-index: ' + zIndex + '"></div>').
1011
+ find('button,select').attr('disabled', 'disabled').end().
1012
+ find('a').removeAttr('href');
1013
+ }
1014
+ else {
1015
+ target.prop('disabled', true);
1016
+ inst.trigger.filter('button.' + this._triggerClass).
1017
+ attr('disabled', 'disabled').end().
1018
+ filter('img.' + this._triggerClass).
1019
+ css({opacity: '0.5', cursor: 'default'});
1020
+ }
1021
+ this._disabled = $.map(this._disabled,
1022
+ function(value) { return (value == target[0] ? null : value); }); // Delete entry
1023
+ this._disabled.push(target[0]);
1024
+ },
1025
+
1026
+ /* Is the first field in a jQuery collection disabled as a datepicker?
1027
+ @param target (element) the control to examine
1028
+ @return (boolean) true if disabled, false if enabled */
1029
+ _isDisabledPlugin: function(target) {
1030
+ return (target && $.inArray(target, this._disabled) > -1);
1031
+ },
1032
+
1033
+ /* Show a popup datepicker.
1034
+ @param target (event) a focus event or
1035
+ (element) the control to use */
1036
+ _showPlugin: function(target) {
1037
+ target = $(target.target || target);
1038
+ var inst = target.data(plugin.propertyName);
1039
+ if (plugin.curInst == inst) {
1040
+ return;
1041
+ }
1042
+ if (plugin.curInst) {
1043
+ plugin._hidePlugin(plugin.curInst, true);
1044
+ }
1045
+ if (inst) {
1046
+ // Retrieve existing date(s)
1047
+ inst.lastVal = null;
1048
+ inst.selectedDates = plugin._extractDates(inst, target.val());
1049
+ inst.pickingRange = false;
1050
+ inst.drawDate = plugin._checkMinMax(plugin.newDate(inst.selectedDates[0] ||
1051
+ inst.get('defaultDate') || plugin.today()), inst);
1052
+ inst.prevDate = plugin.newDate(inst.drawDate);
1053
+ plugin.curInst = inst;
1054
+ // Generate content
1055
+ plugin._update(target[0], true);
1056
+ // Adjust position before showing
1057
+ var offset = plugin._checkOffset(inst);
1058
+ inst.div.css({left: offset.left, top: offset.top});
1059
+ // And display
1060
+ var showAnim = inst.options.showAnim;
1061
+ var showSpeed = inst.options.showSpeed;
1062
+ showSpeed = (showSpeed == 'normal' && $.ui && $.ui.version >= '1.8' ?
1063
+ '_default' : showSpeed);
1064
+ if ($.effects && $.effects[showAnim]) {
1065
+ var data = inst.div.data(); // Update old effects data
1066
+ for (var key in data) {
1067
+ if (key.match(/^ec\.storage\./)) {
1068
+ data[key] = inst._mainDiv.css(key.replace(/ec\.storage\./, ''));
1069
+ }
1070
+ }
1071
+ inst.div.data(data).show(showAnim, inst.options.showOptions, showSpeed);
1072
+ }
1073
+ else {
1074
+ inst.div[showAnim || 'show']((showAnim ? showSpeed : ''));
1075
+ }
1076
+ }
1077
+ },
1078
+
1079
+ /* Extract possible dates from a string.
1080
+ @param inst (object) the current instance settings
1081
+ @param text (string) the text to extract from
1082
+ @return (CDate[]) the extracted dates */
1083
+ _extractDates: function(inst, datesText) {
1084
+ if (datesText == inst.lastVal) {
1085
+ return;
1086
+ }
1087
+ inst.lastVal = datesText;
1088
+ datesText = datesText.split(inst.options.multiSelect ? inst.options.multiSeparator :
1089
+ (inst.options.rangeSelect ? inst.options.rangeSeparator : '\x00'));
1090
+ var dates = [];
1091
+ for (var i = 0; i < datesText.length; i++) {
1092
+ try {
1093
+ var date = plugin.parseDate(inst.options.dateFormat, datesText[i], inst.getConfig());
1094
+ if (date) {
1095
+ var found = false;
1096
+ for (var j = 0; j < dates.length; j++) {
1097
+ if (dates[j].getTime() == date.getTime()) {
1098
+ found = true;
1099
+ break;
1100
+ }
1101
+ }
1102
+ if (!found) {
1103
+ dates.push(date);
1104
+ }
1105
+ }
1106
+ }
1107
+ catch (e) {
1108
+ // Ignore
1109
+ }
1110
+ }
1111
+ dates.splice(inst.options.multiSelect || (inst.options.rangeSelect ? 2 : 1), dates.length);
1112
+ if (inst.options.rangeSelect && dates.length == 1) {
1113
+ dates[1] = dates[0];
1114
+ }
1115
+ return dates;
1116
+ },
1117
+
1118
+ /* Update the datepicker display.
1119
+ @param target (event) a focus event or
1120
+ (element) the control to use
1121
+ @param hidden (boolean) true to initially hide the datepicker */
1122
+ _update: function(target, hidden) {
1123
+ target = $(target.target || target);
1124
+ var inst = target.data(plugin.propertyName);
1125
+ if (inst) {
1126
+ if (inst.inline || plugin.curInst == inst) {
1127
+ if ($.isFunction(inst.options.onChangeMonthYear) && (!inst.prevDate ||
1128
+ inst.prevDate.getFullYear() != inst.drawDate.getFullYear() ||
1129
+ inst.prevDate.getMonth() != inst.drawDate.getMonth())) {
1130
+ inst.options.onChangeMonthYear.apply(target[0],
1131
+ [inst.drawDate.getFullYear(), inst.drawDate.getMonth() + 1]);
1132
+ }
1133
+ }
1134
+ if (inst.inline) {
1135
+ target.html(this._generateContent(target[0], inst));
1136
+ }
1137
+ else if (plugin.curInst == inst) {
1138
+ if (!inst.div) {
1139
+ inst.div = $('<div></div>').addClass(this._popupClass).
1140
+ css({display: (hidden ? 'none' : 'static'), position: 'absolute',
1141
+ left: target.offset().left,
1142
+ top: target.offset().top + target.outerHeight()}).
1143
+ appendTo($(inst.options.popupContainer || 'body'));
1144
+ if ($.fn.mousewheel) {
1145
+ inst.div.mousewheel(this._doMouseWheel);
1146
+ }
1147
+ }
1148
+ inst.div.html(this._generateContent(target[0], inst));
1149
+ target.focus();
1150
+ }
1151
+ }
1152
+ },
1153
+
1154
+ /* Update the input field and any alternate field with the current dates.
1155
+ @param target (element) the control to use
1156
+ @param keyUp (boolean, internal) true if coming from keyUp processing */
1157
+ _updateInput: function(target, keyUp) {
1158
+ var inst = $.data(target, this.propertyName);
1159
+ if (inst) {
1160
+ var value = '';
1161
+ var altValue = '';
1162
+ var sep = (inst.options.multiSelect ? inst.options.multiSeparator :
1163
+ inst.options.rangeSeparator);
1164
+ var altFormat = inst.options.altFormat || inst.options.dateFormat;
1165
+ for (var i = 0; i < inst.selectedDates.length; i++) {
1166
+ value += (keyUp ? '' : (i > 0 ? sep : '') + plugin.formatDate(
1167
+ inst.options.dateFormat, inst.selectedDates[i], inst.getConfig()));
1168
+ altValue += (i > 0 ? sep : '') + plugin.formatDate(
1169
+ altFormat, inst.selectedDates[i], inst.getConfig());
1170
+ }
1171
+ if (!inst.inline && !keyUp) {
1172
+ $(target).val(value);
1173
+ }
1174
+ $(inst.options.altField).val(altValue);
1175
+ if ($.isFunction(inst.options.onSelect) && !keyUp && !inst.inSelect) {
1176
+ inst.inSelect = true; // Prevent endless loops
1177
+ inst.options.onSelect.apply(target, [inst.selectedDates]);
1178
+ inst.inSelect = false;
1179
+ }
1180
+ }
1181
+ },
1182
+
1183
+ /* Retrieve the size of left and top borders for an element.
1184
+ @param elem (jQuery) the element of interest
1185
+ @return (number[2]) the left and top borders */
1186
+ _getBorders: function(elem) {
1187
+ var convert = function(value) {
1188
+ return {thin: 1, medium: 3, thick: 5}[value] || value;
1189
+ };
1190
+ return [parseFloat(convert(elem.css('border-left-width'))),
1191
+ parseFloat(convert(elem.css('border-top-width')))];
1192
+ },
1193
+
1194
+ /* Check positioning to remain on the screen.
1195
+ @param inst (object) the current instance settings
1196
+ @return (object) the updated offset for the datepicker */
1197
+ _checkOffset: function(inst) {
1198
+ var base = (inst.target.is(':hidden') && inst.trigger ? inst.trigger : inst.target);
1199
+ var offset = base.offset();
1200
+ var browserWidth = $(window).width();
1201
+ var browserHeight = $(window).height();
1202
+ if (browserWidth == 0) {
1203
+ return offset;
1204
+ }
1205
+ var isFixed = false;
1206
+ $(inst.target).parents().each(function() {
1207
+ isFixed |= $(this).css('position') == 'fixed';
1208
+ return !isFixed;
1209
+ });
1210
+ var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
1211
+ var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
1212
+ var above = offset.top - (isFixed ? scrollY : 0) - inst.div.outerHeight();
1213
+ var below = offset.top - (isFixed ? scrollY : 0) + base.outerHeight();
1214
+ var alignL = offset.left - (isFixed ? scrollX : 0);
1215
+ var alignR = offset.left - (isFixed ? scrollX : 0) + base.outerWidth() - inst.div.outerWidth();
1216
+ var tooWide = (offset.left - scrollX + inst.div.outerWidth()) > browserWidth;
1217
+ var tooHigh = (offset.top - scrollY + inst.target.outerHeight() +
1218
+ inst.div.outerHeight()) > browserHeight;
1219
+ inst.div.css('position', isFixed ? 'fixed' : 'absolute');
1220
+ var alignment = inst.options.alignment;
1221
+ if (alignment == 'topLeft') {
1222
+ offset = {left: alignL, top: above};
1223
+ }
1224
+ else if (alignment == 'topRight') {
1225
+ offset = {left: alignR, top: above};
1226
+ }
1227
+ else if (alignment == 'bottomLeft') {
1228
+ offset = {left: alignL, top: below};
1229
+ }
1230
+ else if (alignment == 'bottomRight') {
1231
+ offset = {left: alignR, top: below};
1232
+ }
1233
+ else if (alignment == 'top') {
1234
+ offset = {left: (inst.options.isRTL || tooWide ? alignR : alignL), top: above};
1235
+ }
1236
+ else { // bottom
1237
+ offset = {left: (inst.options.isRTL || tooWide ? alignR : alignL),
1238
+ top: (tooHigh ? above : below)};
1239
+ }
1240
+ offset.left = Math.max((isFixed ? 0 : scrollX), offset.left);
1241
+ offset.top = Math.max((isFixed ? 0 : scrollY), offset.top);
1242
+ return offset;
1243
+ },
1244
+
1245
+ /* Close date picker if clicked elsewhere.
1246
+ @param event (MouseEvent) the mouse click to check */
1247
+ _checkExternalClick: function(event) {
1248
+ if (!plugin.curInst) {
1249
+ return;
1250
+ }
1251
+ var target = $(event.target);
1252
+ if (!target.parents().andSelf().hasClass(plugin._popupClass) &&
1253
+ !target.hasClass(plugin.markerClassName) &&
1254
+ !target.parents().andSelf().hasClass(plugin._triggerClass)) {
1255
+ plugin._hidePlugin(plugin.curInst);
1256
+ }
1257
+ },
1258
+
1259
+ /* Hide a popup datepicker.
1260
+ @param target (element) the control to use or
1261
+ (object) the current instance settings
1262
+ @param immediate (boolean) true to close immediately without animation */
1263
+ _hidePlugin: function(target, immediate) {
1264
+ if (!target) {
1265
+ return;
1266
+ }
1267
+ var inst = $.data(target, this.propertyName) || target;
1268
+ if (inst && inst == plugin.curInst) {
1269
+ var showAnim = (immediate ? '' : inst.options.showAnim);
1270
+ var showSpeed = inst.options.showSpeed;
1271
+ showSpeed = (showSpeed == 'normal' && $.ui && $.ui.version >= '1.8' ?
1272
+ '_default' : showSpeed);
1273
+ var postProcess = function() {
1274
+ if (!inst.div) {
1275
+ return;
1276
+ }
1277
+ inst.div.remove();
1278
+ inst.div = null;
1279
+ plugin.curInst = null;
1280
+ if ($.isFunction(inst.options.onClose)) {
1281
+ inst.options.onClose.apply(target, [inst.selectedDates]);
1282
+ }
1283
+ };
1284
+ inst.div.stop();
1285
+ if ($.effects && $.effects[showAnim]) {
1286
+ inst.div.hide(showAnim, inst.options.showOptions, showSpeed, postProcess);
1287
+ }
1288
+ else {
1289
+ var hideAnim = (showAnim == 'slideDown' ? 'slideUp' :
1290
+ (showAnim == 'fadeIn' ? 'fadeOut' : 'hide'));
1291
+ inst.div[hideAnim]((showAnim ? showSpeed : ''), postProcess);
1292
+ }
1293
+ if (!showAnim) {
1294
+ postProcess();
1295
+ }
1296
+ }
1297
+ },
1298
+
1299
+ /* Handle keystrokes in the datepicker.
1300
+ @param event (KeyEvent) the keystroke
1301
+ @return (boolean) true if not handled, false if handled */
1302
+ _keyDown: function(event) {
1303
+ var target = event.target;
1304
+ var inst = $.data(target, plugin.propertyName);
1305
+ var handled = false;
1306
+ if (inst.div) {
1307
+ if (event.keyCode == 9) { // Tab - close
1308
+ plugin._hidePlugin(target);
1309
+ }
1310
+ else if (event.keyCode == 13) { // Enter - select
1311
+ plugin._selectDatePlugin(target,
1312
+ $('a.' + inst.options.renderer.highlightedClass, inst.div)[0]);
1313
+ handled = true;
1314
+ }
1315
+ else { // Command keystrokes
1316
+ var commands = inst.options.commands;
1317
+ for (var name in commands) {
1318
+ var command = commands[name];
1319
+ if (command.keystroke.keyCode == event.keyCode &&
1320
+ !!command.keystroke.ctrlKey == !!(event.ctrlKey || event.metaKey) &&
1321
+ !!command.keystroke.altKey == event.altKey &&
1322
+ !!command.keystroke.shiftKey == event.shiftKey) {
1323
+ plugin._performActionPlugin(target, name);
1324
+ handled = true;
1325
+ break;
1326
+ }
1327
+ }
1328
+ }
1329
+ }
1330
+ else { // Show on 'current' keystroke
1331
+ var command = inst.options.commands.current;
1332
+ if (command.keystroke.keyCode == event.keyCode &&
1333
+ !!command.keystroke.ctrlKey == !!(event.ctrlKey || event.metaKey) &&
1334
+ !!command.keystroke.altKey == event.altKey &&
1335
+ !!command.keystroke.shiftKey == event.shiftKey) {
1336
+ plugin._showPlugin(target);
1337
+ handled = true;
1338
+ }
1339
+ }
1340
+ inst.ctrlKey = ((event.keyCode < 48 && event.keyCode != 32) ||
1341
+ event.ctrlKey || event.metaKey);
1342
+ if (handled) {
1343
+ event.preventDefault();
1344
+ event.stopPropagation();
1345
+ }
1346
+ return !handled;
1347
+ },
1348
+
1349
+ /* Filter keystrokes in the datepicker.
1350
+ @param event (KeyEvent) the keystroke
1351
+ @return (boolean) true if allowed, false if not allowed */
1352
+ _keyPress: function(event) {
1353
+ var inst = $.data(event.target, plugin.propertyName);
1354
+ if (inst && inst.options.constrainInput) {
1355
+ var ch = String.fromCharCode(event.keyCode || event.charCode);
1356
+ var allowedChars = plugin._allowedChars(inst);
1357
+ return (event.metaKey || inst.ctrlKey || ch < ' ' ||
1358
+ !allowedChars || allowedChars.indexOf(ch) > -1);
1359
+ }
1360
+ return true;
1361
+ },
1362
+
1363
+ /* Determine the set of characters allowed by the date format.
1364
+ @param inst (object) the current instance settings
1365
+ @return (string) the set of allowed characters, or null if anything allowed */
1366
+ _allowedChars: function(inst) {
1367
+ var allowedChars = (inst.options.multiSelect ? inst.options.multiSeparator :
1368
+ (inst.options.rangeSelect ? inst.options.rangeSeparator : ''));
1369
+ var literal = false;
1370
+ var hasNum = false;
1371
+ var dateFormat = inst.options.dateFormat;
1372
+ for (var i = 0; i < dateFormat.length; i++) {
1373
+ var ch = dateFormat.charAt(i);
1374
+ if (literal) {
1375
+ if (ch == "'" && dateFormat.charAt(i + 1) != "'") {
1376
+ literal = false;
1377
+ }
1378
+ else {
1379
+ allowedChars += ch;
1380
+ }
1381
+ }
1382
+ else {
1383
+ switch (ch) {
1384
+ case 'd': case 'm': case 'o': case 'w':
1385
+ allowedChars += (hasNum ? '' : '0123456789'); hasNum = true; break;
1386
+ case 'y': case '@': case '!':
1387
+ allowedChars += (hasNum ? '' : '0123456789') + '-'; hasNum = true; break;
1388
+ case 'J':
1389
+ allowedChars += (hasNum ? '' : '0123456789') + '-.'; hasNum = true; break;
1390
+ case 'D': case 'M': case 'Y':
1391
+ return null; // Accept anything
1392
+ case "'":
1393
+ if (dateFormat.charAt(i + 1) == "'") {
1394
+ allowedChars += "'";
1395
+ }
1396
+ else {
1397
+ literal = true;
1398
+ }
1399
+ break;
1400
+ default:
1401
+ allowedChars += ch;
1402
+ }
1403
+ }
1404
+ }
1405
+ return allowedChars;
1406
+ },
1407
+
1408
+ /* Synchronise datepicker with the field.
1409
+ @param event (KeyEvent) the keystroke
1410
+ @return (boolean) true if allowed, false if not allowed */
1411
+ _keyUp: function(event) {
1412
+ var target = event.target;
1413
+ var inst = $.data(target, plugin.propertyName);
1414
+ if (inst && !inst.ctrlKey && inst.lastVal != inst.target.val()) {
1415
+ try {
1416
+ var dates = plugin._extractDates(inst, inst.target.val());
1417
+ if (dates.length > 0) {
1418
+ plugin._setDatePlugin(target, dates, null, true);
1419
+ }
1420
+ }
1421
+ catch (event) {
1422
+ // Ignore
1423
+ }
1424
+ }
1425
+ return true;
1426
+ },
1427
+
1428
+ /* Increment/decrement month/year on mouse wheel activity.
1429
+ @param event (event) the mouse wheel event
1430
+ @param delta (number) the amount of change */
1431
+ _doMouseWheel: function(event, delta) {
1432
+ var target = (plugin.curInst && plugin.curInst.target[0]) ||
1433
+ $(event.target).closest('.' + plugin.markerClassName)[0];
1434
+ if (plugin._isDisabledPlugin(target)) {
1435
+ return;
1436
+ }
1437
+ var inst = $.data(target, plugin.propertyName);
1438
+ if (inst.options.useMouseWheel) {
1439
+ delta = (delta < 0 ? -1 : +1);
1440
+ plugin._changeMonthPlugin(target,
1441
+ -inst.options[event.ctrlKey ? 'monthsToJump' : 'monthsToStep'] * delta);
1442
+ }
1443
+ event.preventDefault();
1444
+ },
1445
+
1446
+ /* Clear an input and close a popup datepicker.
1447
+ @param target (element) the control to use */
1448
+ _clearPlugin: function(target) {
1449
+ var inst = $.data(target, this.propertyName);
1450
+ if (inst) {
1451
+ inst.selectedDates = [];
1452
+ this._hidePlugin(target);
1453
+ var defaultDate = inst.get('defaultDate');
1454
+ if (inst.options.selectDefaultDate && defaultDate) {
1455
+ this._setDatePlugin(target, plugin.newDate(defaultDate || plugin.today()));
1456
+ }
1457
+ else {
1458
+ this._updateInput(target);
1459
+ }
1460
+ }
1461
+ },
1462
+
1463
+ /* Retrieve the selected date(s) for a datepicker.
1464
+ @param target (element) the control to examine
1465
+ @return (CDate[]) the selected date(s) */
1466
+ _getDatePlugin: function(target) {
1467
+ var inst = $.data(target, this.propertyName);
1468
+ return (inst ? inst.selectedDates : []);
1469
+ },
1470
+
1471
+ /* Set the selected date(s) for a datepicker.
1472
+ @param target (element) the control to examine
1473
+ @param dates (CDate or number or string or [] of these) the selected date(s)
1474
+ @param endDate (CDate or number or string) the ending date for a range (optional)
1475
+ @param keyUp (boolean, internal) true if coming from keyUp processing
1476
+ @param setOpt (boolean, internal) true if coming from option processing */
1477
+ _setDatePlugin: function(target, dates, endDate, keyUp, setOpt) {
1478
+ var inst = $.data(target, this.propertyName);
1479
+ if (inst) {
1480
+ if (!$.isArray(dates)) {
1481
+ dates = [dates];
1482
+ if (endDate) {
1483
+ dates.push(endDate);
1484
+ }
1485
+ }
1486
+ var minDate = inst.get('minDate');
1487
+ var maxDate = inst.get('maxDate');
1488
+ var curDate = inst.selectedDates[0];
1489
+ inst.selectedDates = [];
1490
+ for (var i = 0; i < dates.length; i++) {
1491
+ var date = plugin.determineDate(
1492
+ dates[i], null, curDate, inst.options.dateFormat, inst.getConfig());
1493
+ if (date) {
1494
+ if ((!minDate || date.getTime() >= minDate.getTime()) &&
1495
+ (!maxDate || date.getTime() <= maxDate.getTime())) {
1496
+ var found = false;
1497
+ for (var j = 0; j < inst.selectedDates.length; j++) {
1498
+ if (inst.selectedDates[j].getTime() == date.getTime()) {
1499
+ found = true;
1500
+ break;
1501
+ }
1502
+ }
1503
+ if (!found) {
1504
+ inst.selectedDates.push(date);
1505
+ }
1506
+ }
1507
+ }
1508
+ }
1509
+ inst.selectedDates.splice(inst.options.multiSelect ||
1510
+ (inst.options.rangeSelect ? 2 : 1), inst.selectedDates.length);
1511
+ if (inst.options.rangeSelect) {
1512
+ switch (inst.selectedDates.length) {
1513
+ case 1: inst.selectedDates[1] = inst.selectedDates[0]; break;
1514
+ case 2: inst.selectedDates[1] =
1515
+ (inst.selectedDates[0].getTime() > inst.selectedDates[1].getTime() ?
1516
+ inst.selectedDates[0] : inst.selectedDates[1]); break;
1517
+ }
1518
+ inst.pickingRange = false;
1519
+ }
1520
+ inst.prevDate = (inst.drawDate ? plugin.newDate(inst.drawDate) : null);
1521
+ inst.drawDate = this._checkMinMax(plugin.newDate(inst.selectedDates[0] ||
1522
+ inst.get('defaultDate') || plugin.today()), inst);
1523
+ if (!setOpt) {
1524
+ this._update(target);
1525
+ this._updateInput(target, keyUp);
1526
+ }
1527
+ }
1528
+ },
1529
+
1530
+ /* Determine whether a date is selectable for this datepicker.
1531
+ @param target (element) the control to check
1532
+ @param date (Date or string or number) the date to check
1533
+ @return (boolean) true if selectable, false if not */
1534
+ _isSelectablePlugin: function(target, date) {
1535
+ var inst = $.data(target, this.propertyName);
1536
+ if (!inst) {
1537
+ return false;
1538
+ }
1539
+ date = plugin.determineDate(date, inst.selectedDates[0] || this.today(), null,
1540
+ inst.options.dateFormat, inst.getConfig());
1541
+ return this._isSelectable(target, date, inst.options.onDate,
1542
+ inst.get('minDate'), inst.get('maxDate'));
1543
+ },
1544
+
1545
+ /* Internally determine whether a date is selectable for this datepicker.
1546
+ @param target (element) the control to check
1547
+ @param date (Date) the date to check
1548
+ @param onDate (function or boolean) any onDate callback or callback.selectable
1549
+ @param mindate (Date) the minimum allowed date
1550
+ @param maxdate (Date) the maximum allowed date
1551
+ @return (boolean) true if selectable, false if not */
1552
+ _isSelectable: function(target, date, onDate, minDate, maxDate) {
1553
+ var dateInfo = (typeof onDate == 'boolean' ? {selectable: onDate} :
1554
+ (!$.isFunction(onDate) ? {} : onDate.apply(target, [date, true])));
1555
+ return (dateInfo.selectable != false) &&
1556
+ (!minDate || date.getTime() >= minDate.getTime()) &&
1557
+ (!maxDate || date.getTime() <= maxDate.getTime());
1558
+ },
1559
+
1560
+ /* Perform a named action for a datepicker.
1561
+ @param target (element) the control to affect
1562
+ @param action (string) the name of the action */
1563
+ _performActionPlugin: function(target, action) {
1564
+ var inst = $.data(target, this.propertyName);
1565
+ if (inst && !this._isDisabledPlugin(target)) {
1566
+ var commands = inst.options.commands;
1567
+ if (commands[action] && commands[action].enabled.apply(target, [inst])) {
1568
+ commands[action].action.apply(target, [inst]);
1569
+ }
1570
+ }
1571
+ },
1572
+
1573
+ /* Set the currently shown month, defaulting to today's.
1574
+ @param target (element) the control to affect
1575
+ @param year (number) the year to show (optional)
1576
+ @param month (number) the month to show (1-12) (optional)
1577
+ @param day (number) the day to show (optional) */
1578
+ _showMonthPlugin: function(target, year, month, day) {
1579
+ var inst = $.data(target, this.propertyName);
1580
+ if (inst && (day != null ||
1581
+ (inst.drawDate.getFullYear() != year || inst.drawDate.getMonth() + 1 != month))) {
1582
+ inst.prevDate = plugin.newDate(inst.drawDate);
1583
+ var show = this._checkMinMax((year != null ?
1584
+ plugin.newDate(year, month, 1) : plugin.today()), inst);
1585
+ inst.drawDate = plugin.newDate(show.getFullYear(), show.getMonth() + 1,
1586
+ (day != null ? day : Math.min(inst.drawDate.getDate(),
1587
+ plugin.daysInMonth(show.getFullYear(), show.getMonth() + 1))));
1588
+ this._update(target);
1589
+ }
1590
+ },
1591
+
1592
+ /* Adjust the currently shown month.
1593
+ @param target (element) the control to affect
1594
+ @param offset (number) the number of months to change by */
1595
+ _changeMonthPlugin: function(target, offset) {
1596
+ var inst = $.data(target, this.propertyName);
1597
+ if (inst) {
1598
+ var date = plugin.add(plugin.newDate(inst.drawDate), offset, 'm');
1599
+ this._showMonthPlugin(target, date.getFullYear(), date.getMonth() + 1);
1600
+ }
1601
+ },
1602
+
1603
+ /* Adjust the currently shown day.
1604
+ @param target (element) the control to affect
1605
+ @param offset (number) the number of days to change by */
1606
+ _changeDayPlugin: function(target, offset) {
1607
+ var inst = $.data(target, this.propertyName);
1608
+ if (inst) {
1609
+ var date = plugin.add(plugin.newDate(inst.drawDate), offset, 'd');
1610
+ this._showMonthPlugin(target, date.getFullYear(), date.getMonth() + 1, date.getDate());
1611
+ }
1612
+ },
1613
+
1614
+ /* Restrict a date to the minimum/maximum specified.
1615
+ @param date (CDate) the date to check
1616
+ @param inst (object) the current instance settings */
1617
+ _checkMinMax: function(date, inst) {
1618
+ var minDate = inst.get('minDate');
1619
+ var maxDate = inst.get('maxDate');
1620
+ date = (minDate && date.getTime() < minDate.getTime() ? plugin.newDate(minDate) : date);
1621
+ date = (maxDate && date.getTime() > maxDate.getTime() ? plugin.newDate(maxDate) : date);
1622
+ return date;
1623
+ },
1624
+
1625
+ /* Retrieve the date associated with an entry in the datepicker.
1626
+ @param target (element) the control to examine
1627
+ @param elem (element) the selected datepicker element
1628
+ @return (CDate) the corresponding date, or null */
1629
+ _retrieveDatePlugin: function(target, elem) {
1630
+ var inst = $.data(target, this.propertyName);
1631
+ return (!inst ? null : this._normaliseDate(
1632
+ new Date(parseInt(elem.className.replace(/^.*dp(-?\d+).*$/, '$1'), 10))));
1633
+ },
1634
+
1635
+ /* Select a date for this datepicker.
1636
+ @param target (element) the control to examine
1637
+ @param elem (element) the selected datepicker element */
1638
+ _selectDatePlugin: function(target, elem) {
1639
+ var inst = $.data(target, this.propertyName);
1640
+ if (inst && !this._isDisabledPlugin(target)) {
1641
+ var date = this._retrieveDatePlugin(target, elem);
1642
+ if (inst.options.multiSelect) {
1643
+ var found = false;
1644
+ for (var i = 0; i < inst.selectedDates.length; i++) {
1645
+ if (date.getTime() == inst.selectedDates[i].getTime()) {
1646
+ inst.selectedDates.splice(i, 1);
1647
+ found = true;
1648
+ break;
1649
+ }
1650
+ }
1651
+ if (!found && inst.selectedDates.length < inst.options.multiSelect) {
1652
+ inst.selectedDates.push(date);
1653
+ }
1654
+ }
1655
+ else if (inst.options.rangeSelect) {
1656
+ if (inst.pickingRange) {
1657
+ inst.selectedDates[1] = date;
1658
+ }
1659
+ else {
1660
+ inst.selectedDates = [date, date];
1661
+ }
1662
+ inst.pickingRange = !inst.pickingRange;
1663
+ }
1664
+ else {
1665
+ inst.selectedDates = [date];
1666
+ }
1667
+ inst.prevDate = plugin.newDate(date);
1668
+ this._updateInput(target);
1669
+ if (inst.inline || inst.pickingRange || inst.selectedDates.length <
1670
+ (inst.options.multiSelect || (inst.options.rangeSelect ? 2 : 1))) {
1671
+ this._update(target);
1672
+ }
1673
+ else {
1674
+ this._hidePlugin(target);
1675
+ }
1676
+ }
1677
+ },
1678
+
1679
+ /* Generate the datepicker content for this control.
1680
+ @param target (element) the control to affect
1681
+ @param inst (object) the current instance settings
1682
+ @return (jQuery) the datepicker content */
1683
+ _generateContent: function(target, inst) {
1684
+ var monthsToShow = inst.options.monthsToShow;
1685
+ monthsToShow = ($.isArray(monthsToShow) ? monthsToShow : [1, monthsToShow]);
1686
+ inst.drawDate = this._checkMinMax(
1687
+ inst.drawDate || inst.get('defaultDate') || plugin.today(), inst);
1688
+ var drawDate = plugin._applyMonthsOffset(plugin.newDate(inst.drawDate), inst);
1689
+ // Generate months
1690
+ var monthRows = '';
1691
+ for (var row = 0; row < monthsToShow[0]; row++) {
1692
+ var months = '';
1693
+ for (var col = 0; col < monthsToShow[1]; col++) {
1694
+ months += this._generateMonth(target, inst, drawDate.getFullYear(),
1695
+ drawDate.getMonth() + 1, inst.options.renderer, (row == 0 && col == 0));
1696
+ plugin.add(drawDate, 1, 'm');
1697
+ }
1698
+ monthRows += this._prepare(inst.options.renderer.monthRow, inst).replace(/\{months\}/, months);
1699
+ }
1700
+ var picker = this._prepare(inst.options.renderer.picker, inst).replace(/\{months\}/, monthRows).
1701
+ replace(/\{weekHeader\}/g, this._generateDayHeaders(inst, inst.options.renderer));
1702
+ // Add commands
1703
+ var addCommand = function(type, open, close, name, classes) {
1704
+ if (picker.indexOf('{' + type + ':' + name + '}') == -1) {
1705
+ return;
1706
+ }
1707
+ var command = inst.options.commands[name];
1708
+ var date = (inst.options.commandsAsDateFormat ? command.date.apply(target, [inst]) : null);
1709
+ picker = picker.replace(new RegExp('\\{' + type + ':' + name + '\\}', 'g'),
1710
+ '<' + open +
1711
+ (command.status ? ' title="' + inst.options[command.status] + '"' : '') +
1712
+ ' class="' + inst.options.renderer.commandClass + ' ' +
1713
+ inst.options.renderer.commandClass + '-' + name + ' ' + classes +
1714
+ (command.enabled(inst) ? '' : ' ' + inst.options.renderer.disabledClass) + '">' +
1715
+ (date ? plugin.formatDate(inst.options[command.text], date, inst.getConfig()) :
1716
+ inst.options[command.text]) + '</' + close + '>');
1717
+ };
1718
+ for (var name in inst.options.commands) {
1719
+ addCommand('button', 'button type="button"', 'button', name,
1720
+ inst.options.renderer.commandButtonClass);
1721
+ addCommand('link', 'a href="javascript:void(0)"', 'a', name,
1722
+ inst.options.renderer.commandLinkClass);
1723
+ }
1724
+ picker = $(picker);
1725
+ if (monthsToShow[1] > 1) {
1726
+ var count = 0;
1727
+ $(inst.options.renderer.monthSelector, picker).each(function() {
1728
+ var nth = ++count % monthsToShow[1];
1729
+ $(this).addClass(nth == 1 ? 'first' : (nth == 0 ? 'last' : ''));
1730
+ });
1731
+ }
1732
+ // Add datepicker behaviour
1733
+ var self = this;
1734
+ picker.find(inst.options.renderer.daySelector + ' a').hover(
1735
+ function() { $(this).addClass(inst.options.renderer.highlightedClass); },
1736
+ function() {
1737
+ (inst.inline ? $(this).parents('.' + self.markerClassName) : inst.div).
1738
+ find(inst.options.renderer.daySelector + ' a').
1739
+ removeClass(inst.options.renderer.highlightedClass);
1740
+ }).
1741
+ click(function() {
1742
+ self._selectDatePlugin(target, this);
1743
+ }).end().
1744
+ find('select.' + this._monthYearClass + ':not(.' + this._anyYearClass + ')').
1745
+ change(function() {
1746
+ var monthYear = $(this).val().split('/');
1747
+ self._showMonthPlugin(target, parseInt(monthYear[1], 10), parseInt(monthYear[0], 10));
1748
+ }).end().
1749
+ find('select.' + this._anyYearClass).click(function() {
1750
+ $(this).css('visibility', 'hidden').
1751
+ next('input').css({left: this.offsetLeft, top: this.offsetTop,
1752
+ width: this.offsetWidth, height: this.offsetHeight}).show().focus();
1753
+ }).end().
1754
+ find('input.' + self._monthYearClass).change(function() {
1755
+ try {
1756
+ var year = parseInt($(this).val(), 10);
1757
+ year = (isNaN(year) ? inst.drawDate.getFullYear() : year);
1758
+ self._showMonthPlugin(target, year, inst.drawDate.getMonth() + 1, inst.drawDate.getDate());
1759
+ }
1760
+ catch (e) {
1761
+ alert(e);
1762
+ }
1763
+ }).keydown(function(event) {
1764
+ if (event.keyCode == 13) { // Enter
1765
+ $(event.target).change();
1766
+ }
1767
+ else if (event.keyCode == 27) { // Escape
1768
+ $(event.target).hide().prev('select').css('visibility', 'visible');
1769
+ inst.target.focus();
1770
+ }
1771
+ });
1772
+ // Add command behaviour
1773
+ picker.find('.' + inst.options.renderer.commandClass).click(function() {
1774
+ if (!$(this).hasClass(inst.options.renderer.disabledClass)) {
1775
+ var action = this.className.replace(
1776
+ new RegExp('^.*' + inst.options.renderer.commandClass + '-([^ ]+).*$'), '$1');
1777
+ plugin._performActionPlugin(target, action);
1778
+ }
1779
+ });
1780
+ // Add classes
1781
+ if (inst.options.isRTL) {
1782
+ picker.addClass(inst.options.renderer.rtlClass);
1783
+ }
1784
+ if (monthsToShow[0] * monthsToShow[1] > 1) {
1785
+ picker.addClass(inst.options.renderer.multiClass);
1786
+ }
1787
+ if (inst.options.pickerClass) {
1788
+ picker.addClass(inst.options.pickerClass);
1789
+ }
1790
+ // Resize
1791
+ $('body').append(picker);
1792
+ var width = 0;
1793
+ picker.find(inst.options.renderer.monthSelector).each(function() {
1794
+ width += $(this).outerWidth();
1795
+ });
1796
+ picker.width(width / monthsToShow[0]);
1797
+ // Pre-show customisation
1798
+ if ($.isFunction(inst.options.onShow)) {
1799
+ inst.options.onShow.apply(target, [picker, inst]);
1800
+ }
1801
+ return picker;
1802
+ },
1803
+
1804
+ /* Generate the content for a single month.
1805
+ @param target (element) the control to affect
1806
+ @param inst (object) the current instance settings
1807
+ @param year (number) the year to generate
1808
+ @param month (number) the month to generate
1809
+ @param renderer (object) the rendering templates
1810
+ @param first (boolean) true if first of multiple months
1811
+ @return (string) the month content */
1812
+ _generateMonth: function(target, inst, year, month, renderer, first) {
1813
+ var daysInMonth = plugin.daysInMonth(year, month);
1814
+ var monthsToShow = inst.options.monthsToShow;
1815
+ monthsToShow = ($.isArray(monthsToShow) ? monthsToShow : [1, monthsToShow]);
1816
+ var fixedWeeks = inst.options.fixedWeeks || (monthsToShow[0] * monthsToShow[1] > 1);
1817
+ var firstDay = inst.options.firstDay;
1818
+ var leadDays = (plugin.newDate(year, month, 1).getDay() - firstDay + 7) % 7;
1819
+ var numWeeks = (fixedWeeks ? 6 : Math.ceil((leadDays + daysInMonth) / 7));
1820
+ var selectOtherMonths = inst.options.selectOtherMonths && inst.options.showOtherMonths;
1821
+ var minDate = (inst.pickingRange ? inst.selectedDates[0] : inst.get('minDate'));
1822
+ var maxDate = inst.get('maxDate');
1823
+ var showWeeks = renderer.week.indexOf('{weekOfYear}') > -1;
1824
+ var today = plugin.today();
1825
+ var drawDate = plugin.newDate(year, month, 1);
1826
+ plugin.add(drawDate, -leadDays - (fixedWeeks && (drawDate.getDay() == firstDay) ? 7 : 0), 'd');
1827
+ var ts = drawDate.getTime();
1828
+ // Generate weeks
1829
+ var weeks = '';
1830
+ for (var week = 0; week < numWeeks; week++) {
1831
+ var weekOfYear = (!showWeeks ? '' : '<span class="dp' + ts + '">' +
1832
+ ($.isFunction(inst.options.calculateWeek) ? inst.options.calculateWeek(drawDate) : 0) + '</span>');
1833
+ var days = '';
1834
+ for (var day = 0; day < 7; day++) {
1835
+ var selected = false;
1836
+ if (inst.options.rangeSelect && inst.selectedDates.length > 0) {
1837
+ selected = (drawDate.getTime() >= inst.selectedDates[0] &&
1838
+ drawDate.getTime() <= inst.selectedDates[1]);
1839
+ }
1840
+ else {
1841
+ for (var i = 0; i < inst.selectedDates.length; i++) {
1842
+ if (inst.selectedDates[i].getTime() == drawDate.getTime()) {
1843
+ selected = true;
1844
+ break;
1845
+ }
1846
+ }
1847
+ }
1848
+ var dateInfo = (!$.isFunction(inst.options.onDate) ? {} :
1849
+ inst.options.onDate.apply(target, [drawDate, drawDate.getMonth() + 1 == month]));
1850
+ var selectable = (selectOtherMonths || drawDate.getMonth() + 1 == month) &&
1851
+ this._isSelectable(target, drawDate, dateInfo.selectable, minDate, maxDate);
1852
+ days += this._prepare(renderer.day, inst).replace(/\{day\}/g,
1853
+ (selectable ? '<a href="javascript:void(0)"' : '<span') +
1854
+ ' class="dp' + ts + ' ' + (dateInfo.dateClass || '') +
1855
+ (selected && (selectOtherMonths || drawDate.getMonth() + 1 == month) ?
1856
+ ' ' + renderer.selectedClass : '') +
1857
+ (selectable ? ' ' + renderer.defaultClass : '') +
1858
+ ((drawDate.getDay() || 7) < 6 ? '' : ' ' + renderer.weekendClass) +
1859
+ (drawDate.getMonth() + 1 == month ? '' : ' ' + renderer.otherMonthClass) +
1860
+ (drawDate.getTime() == today.getTime() && (drawDate.getMonth() + 1) == month ?
1861
+ ' ' + renderer.todayClass : '') +
1862
+ (drawDate.getTime() == inst.drawDate.getTime() && (drawDate.getMonth() + 1) == month ?
1863
+ ' ' + renderer.highlightedClass : '') + '"' +
1864
+ (dateInfo.title || (inst.options.dayStatus && selectable) ? ' title="' +
1865
+ (dateInfo.title || plugin.formatDate(
1866
+ inst.options.dayStatus, drawDate, inst.getConfig())) + '"' : '') + '>' +
1867
+ (inst.options.showOtherMonths || (drawDate.getMonth() + 1) == month ?
1868
+ dateInfo.content || drawDate.getDate() : '&nbsp;') +
1869
+ (selectable ? '</a>' : '</span>'));
1870
+ plugin.add(drawDate, 1, 'd');
1871
+ ts = drawDate.getTime();
1872
+ }
1873
+ weeks += this._prepare(renderer.week, inst).replace(/\{days\}/g, days).
1874
+ replace(/\{weekOfYear\}/g, weekOfYear);
1875
+ }
1876
+ var monthHeader = this._prepare(renderer.month, inst).match(/\{monthHeader(:[^\}]+)?\}/);
1877
+ monthHeader = (monthHeader[0].length <= 13 ? 'MM yyyy' :
1878
+ monthHeader[0].substring(13, monthHeader[0].length - 1));
1879
+ monthHeader = (first ? this._generateMonthSelection(
1880
+ inst, year, month, minDate, maxDate, monthHeader, renderer) :
1881
+ plugin.formatDate(monthHeader, plugin.newDate(year, month, 1), inst.getConfig()));
1882
+ var weekHeader = this._prepare(renderer.weekHeader, inst).
1883
+ replace(/\{days\}/g, this._generateDayHeaders(inst, renderer));
1884
+ return this._prepare(renderer.month, inst).replace(/\{monthHeader(:[^\}]+)?\}/g, monthHeader).
1885
+ replace(/\{weekHeader\}/g, weekHeader).replace(/\{weeks\}/g, weeks);
1886
+ },
1887
+
1888
+ /* Generate the HTML for the day headers.
1889
+ @param inst (object) the current instance settings
1890
+ @param renderer (object) the rendering templates
1891
+ @return (string) a week's worth of day headers */
1892
+ _generateDayHeaders: function(inst, renderer) {
1893
+ var header = '';
1894
+ for (var day = 0; day < 7; day++) {
1895
+ var dow = (day + inst.options.firstDay) % 7;
1896
+ header += this._prepare(renderer.dayHeader, inst).replace(/\{day\}/g,
1897
+ '<span class="' + this._curDoWClass + dow + '" title="' +
1898
+ inst.options.dayNames[dow] + '">' + inst.options.dayNamesMin[dow] + '</span>');
1899
+ }
1900
+ return header;
1901
+ },
1902
+
1903
+ /* Generate selection controls for month.
1904
+ @param inst (object) the current instance settings
1905
+ @param year (number) the year to generate
1906
+ @param month (number) the month to generate
1907
+ @param minDate (CDate) the minimum date allowed
1908
+ @param maxDate (CDate) the maximum date allowed
1909
+ @param monthHeader (string) the month/year format
1910
+ @return (string) the month selection content */
1911
+ _generateMonthSelection: function(inst, year, month, minDate, maxDate, monthHeader) {
1912
+ if (!inst.options.changeMonth) {
1913
+ return plugin.formatDate(
1914
+ monthHeader, plugin.newDate(year, month, 1), inst.getConfig());
1915
+ }
1916
+ // Months
1917
+ var monthNames = inst.options['monthNames' + (monthHeader.match(/mm/i) ? '' : 'Short')];
1918
+ var html = monthHeader.replace(/m+/i, '\\x2E').replace(/y+/i, '\\x2F');
1919
+ var selector = '<select class="' + this._monthYearClass +
1920
+ '" title="' + inst.options.monthStatus + '">';
1921
+ for (var m = 1; m <= 12; m++) {
1922
+ if ((!minDate || plugin.newDate(year, m, plugin.daysInMonth(year, m)).
1923
+ getTime() >= minDate.getTime()) &&
1924
+ (!maxDate || plugin.newDate(year, m, 1).getTime() <= maxDate.getTime())) {
1925
+ selector += '<option value="' + m + '/' + year + '"' +
1926
+ (month == m ? ' selected="selected"' : '') + '>' +
1927
+ monthNames[m - 1] + '</option>';
1928
+ }
1929
+ }
1930
+ selector += '</select>';
1931
+ html = html.replace(/\\x2E/, selector);
1932
+ // Years
1933
+ var yearRange = inst.options.yearRange;
1934
+ if (yearRange == 'any') {
1935
+ selector = '<select class="' + this._monthYearClass + ' ' + this._anyYearClass +
1936
+ '" title="' + inst.options.yearStatus + '">' +
1937
+ '<option>' + year + '</option></select>' +
1938
+ '<input class="' + this._monthYearClass + ' ' + this._curMonthClass +
1939
+ month + '" value="' + year + '">';
1940
+ }
1941
+ else {
1942
+ yearRange = yearRange.split(':');
1943
+ var todayYear = plugin.today().getFullYear();
1944
+ var start = (yearRange[0].match('c[+-].*') ? year + parseInt(yearRange[0].substring(1), 10) :
1945
+ ((yearRange[0].match('[+-].*') ? todayYear : 0) + parseInt(yearRange[0], 10)));
1946
+ var end = (yearRange[1].match('c[+-].*') ? year + parseInt(yearRange[1].substring(1), 10) :
1947
+ ((yearRange[1].match('[+-].*') ? todayYear : 0) + parseInt(yearRange[1], 10)));
1948
+ selector = '<select class="' + this._monthYearClass +
1949
+ '" title="' + inst.options.yearStatus + '">';
1950
+ start = plugin.add(plugin.newDate(start + 1, 1, 1), -1, 'd');
1951
+ end = plugin.newDate(end, 1, 1);
1952
+ var addYear = function(y) {
1953
+ if (y != 0) {
1954
+ selector += '<option value="' + month + '/' + y + '"' +
1955
+ (year == y ? ' selected="selected"' : '') + '>' + y + '</option>';
1956
+ }
1957
+ };
1958
+ if (start.getTime() < end.getTime()) {
1959
+ start = (minDate && minDate.getTime() > start.getTime() ? minDate : start).getFullYear();
1960
+ end = (maxDate && maxDate.getTime() < end.getTime() ? maxDate : end).getFullYear();
1961
+ for (var y = start; y <= end; y++) {
1962
+ addYear(y);
1963
+ }
1964
+ }
1965
+ else {
1966
+ start = (maxDate && maxDate.getTime() < start.getTime() ? maxDate : start).getFullYear();
1967
+ end = (minDate && minDate.getTime() > end.getTime() ? minDate : end).getFullYear();
1968
+ for (var y = start; y >= end; y--) {
1969
+ addYear(y);
1970
+ }
1971
+ }
1972
+ selector += '</select>';
1973
+ }
1974
+ html = html.replace(/\\x2F/, selector);
1975
+ return html;
1976
+ },
1977
+
1978
+ /* Prepare a render template for use.
1979
+ Exclude popup/inline sections that are not applicable.
1980
+ Localise text of the form: {l10n:name}.
1981
+ @param text (string) the text to localise
1982
+ @param inst (object) the current instance settings
1983
+ @return (string) the localised text */
1984
+ _prepare: function(text, inst) {
1985
+ var replaceSection = function(type, retain) {
1986
+ while (true) {
1987
+ var start = text.indexOf('{' + type + ':start}');
1988
+ if (start == -1) {
1989
+ return;
1990
+ }
1991
+ var end = text.substring(start).indexOf('{' + type + ':end}');
1992
+ if (end > -1) {
1993
+ text = text.substring(0, start) +
1994
+ (retain ? text.substr(start + type.length + 8, end - type.length - 8) : '') +
1995
+ text.substring(start + end + type.length + 6);
1996
+ }
1997
+ }
1998
+ };
1999
+ replaceSection('inline', inst.inline);
2000
+ replaceSection('popup', !inst.inline);
2001
+ var pattern = /\{l10n:([^\}]+)\}/;
2002
+ var matches = null;
2003
+ while (matches = pattern.exec(text)) {
2004
+ text = text.replace(matches[0], inst.options[matches[1]]);
2005
+ }
2006
+ return text;
2007
+ }
2008
+ });
2009
+
2010
+ // The list of commands that return values and don't permit chaining
2011
+ var getters = ['getDate', 'isDisabled', 'isSelectable', 'retrieveDate'];
2012
+
2013
+ /* Determine whether a command is a getter and doesn't permit chaining.
2014
+ @param command (string, optional) the command to run
2015
+ @param otherArgs ([], optional) any other arguments for the command
2016
+ @return true if the command is a getter, false if not */
2017
+ function isNotChained(command, otherArgs) {
2018
+ if (command == 'option' && (otherArgs.length == 0 ||
2019
+ (otherArgs.length == 1 && typeof otherArgs[0] == 'string'))) {
2020
+ return true;
2021
+ }
2022
+ return $.inArray(command, getters) > -1;
2023
+ }
2024
+
2025
+ /* Attach the datepicker functionality to a jQuery selection.
2026
+ @param options (object) the new settings to use for these instances (optional) or
2027
+ (string) the command to run (optional)
2028
+ @return (jQuery) for chaining further calls or
2029
+ (any) getter value */
2030
+ $.fn.datepick = function(options) {
2031
+ var otherArgs = Array.prototype.slice.call(arguments, 1);
2032
+ if (isNotChained(options, otherArgs)) {
2033
+ return plugin['_' + options + 'Plugin'].apply(plugin, [this[0]].concat(otherArgs));
2034
+ }
2035
+ return this.each(function() {
2036
+ if (typeof options == 'string') {
2037
+ if (!plugin['_' + options + 'Plugin']) {
2038
+ throw 'Unknown command: ' + options;
2039
+ }
2040
+ plugin['_' + options + 'Plugin'].apply(plugin, [this].concat(otherArgs));
2041
+ }
2042
+ else {
2043
+ plugin._attachPlugin(this, options || {});
2044
+ }
2045
+ });
2046
+ };
2047
+
2048
+ /* Initialise the datepicker functionality. */
2049
+ var plugin = $.datepick = new Datepicker(); // Singleton instance
2050
+
2051
+ $(function() {
2052
+ $(document).mousedown(plugin._checkExternalClick).
2053
+ resize(function() { plugin._hidePlugin(plugin.curInst); });
2054
+ });
2055
+
2056
+ })(jQuery);