watch_tower 0.0.0.1 → 0.0.1.beta1

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