taskwarrior-web 1.0.8 → 1.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. data/CHANGELOG.md +6 -0
  2. data/README.md +5 -1
  3. data/lib/taskwarrior-web/app.rb +14 -41
  4. data/lib/taskwarrior-web/helpers.rb +1 -1
  5. data/lib/taskwarrior-web/public/css/datepicker.css +230 -112
  6. data/lib/taskwarrior-web/public/js/application.js +2 -1
  7. data/lib/taskwarrior-web/public/js/bootstrap-datepicker.js +570 -135
  8. data/lib/taskwarrior-web/{command_builders → services/builder}/base.rb +3 -3
  9. data/lib/taskwarrior-web/{command_builders → services/builder}/v1.rb +1 -2
  10. data/lib/taskwarrior-web/{command_builders → services/builder}/v2.rb +0 -0
  11. data/lib/taskwarrior-web/{command_builder.rb → services/builder.rb} +3 -3
  12. data/lib/taskwarrior-web/{parser → services/parser}/csv.rb +0 -0
  13. data/lib/taskwarrior-web/{parser → services/parser}/json.rb +0 -0
  14. data/lib/taskwarrior-web/{parser.rb → services/parser.rb} +2 -2
  15. data/lib/taskwarrior-web/{runner.rb → services/runner.rb} +0 -0
  16. data/lib/taskwarrior-web/task.rb +11 -1
  17. data/lib/taskwarrior-web/views/projects.erb +31 -33
  18. data/lib/taskwarrior-web/views/task_form.erb +4 -4
  19. data/lib/taskwarrior-web.rb +3 -3
  20. data/spec/app/app_spec.rb +68 -1
  21. data/spec/app/helpers_spec.rb +6 -0
  22. data/spec/models/task_spec.rb +20 -0
  23. data/spec/{models/command_builders → services/builder}/base_spec.rb +19 -2
  24. data/spec/{models/command_builders → services/builder}/v1_spec.rb +7 -4
  25. data/spec/{models/command_builder_spec.rb → services/builder_spec.rb} +1 -1
  26. data/spec/services/parser_spec.rb +14 -0
  27. data/spec/{models → services}/runner_spec.rb +1 -1
  28. data/taskwarrior-web.gemspec +1 -1
  29. metadata +39 -39
  30. data/spec/models/command_builders/v2_spec.rb +0 -0
@@ -1,8 +1,9 @@
1
1
  /* =========================================================
2
- * bootstrap-datepicker.js
2
+ * bootstrap-datepicker.js
3
3
  * http://www.eyecon.ro/bootstrap-datepicker
4
4
  * =========================================================
5
5
  * Copyright 2012 Stefan Petre
6
+ * Improvements by Andrew Rowls
6
7
  *
7
8
  * Licensed under the Apache License, Version 2.0 (the "License");
8
9
  * you may not use this file except in compliance with the License.
@@ -16,177 +17,315 @@
16
17
  * See the License for the specific language governing permissions and
17
18
  * limitations under the License.
18
19
  * ========================================================= */
19
-
20
+
20
21
  !function( $ ) {
21
-
22
+
23
+ function UTCDate(){
24
+ return new Date(Date.UTC.apply(Date, arguments));
25
+ }
26
+ function UTCToday(){
27
+ var today = new Date();
28
+ return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
29
+ }
30
+
22
31
  // Picker object
23
-
24
- var Datepicker = function(element, options){
32
+
33
+ var Datepicker = function(element, options) {
34
+ var that = this;
35
+
25
36
  this.element = $(element);
37
+ this.language = options.language||this.element.data('date-language')||"en";
38
+ this.language = this.language in dates ? this.language : "en";
26
39
  this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
27
40
  this.picker = $(DPGlobal.template)
28
41
  .appendTo('body')
29
42
  .on({
30
- click: $.proxy(this.click, this),
31
- mousedown: $.proxy(this.mousedown, this)
43
+ click: $.proxy(this.click, this)
32
44
  });
33
45
  this.isInput = this.element.is('input');
34
46
  this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
35
-
47
+ this.hasInput = this.component && this.element.find('input').length;
48
+ if(this.component && this.component.length === 0)
49
+ this.component = false;
50
+
36
51
  if (this.isInput) {
37
52
  this.element.on({
38
53
  focus: $.proxy(this.show, this),
39
- blur: $.proxy(this.hide, this),
40
- keyup: $.proxy(this.update, this)
54
+ keyup: $.proxy(this.update, this),
55
+ keydown: $.proxy(this.keydown, this)
41
56
  });
42
57
  } else {
43
- if (this.component){
58
+ if (this.component && this.hasInput){
59
+ // For components that are not readonly, allow keyboard nav
60
+ this.element.find('input').on({
61
+ focus: $.proxy(this.show, this),
62
+ keyup: $.proxy(this.update, this),
63
+ keydown: $.proxy(this.keydown, this)
64
+ });
65
+
44
66
  this.component.on('click', $.proxy(this.show, this));
45
67
  } else {
46
68
  this.element.on('click', $.proxy(this.show, this));
47
69
  }
48
70
  }
49
-
50
- this.viewMode = 0;
51
- this.weekStart = options.weekStart||this.element.data('date-weekstart')||0;
52
- this.weekEnd = this.weekStart == 0 ? 6 : this.weekStart - 1;
71
+
72
+ $(document).on('mousedown', function (e) {
73
+ // Clicked outside the datepicker, hide it
74
+ if ($(e.target).closest('.datepicker').length == 0) {
75
+ that.hide();
76
+ }
77
+ });
78
+
79
+ this.autoclose = false;
80
+ if ('autoclose' in options) {
81
+ this.autoclose = options.autoclose;
82
+ } else if ('dateAutoclose' in this.element.data()) {
83
+ this.autoclose = this.element.data('date-autoclose');
84
+ }
85
+
86
+ this.keyboardNavigation = true;
87
+ if ('keyboardNavigation' in options) {
88
+ this.keyboardNavigation = options.keyboardNavigation;
89
+ } else if ('dateKeyboardNavigation' in this.element.data()) {
90
+ this.keyboardNavigation = this.element.data('date-keyboard-navigation');
91
+ }
92
+
93
+ switch(options.startView || this.element.data('date-start-view')){
94
+ case 2:
95
+ case 'decade':
96
+ this.viewMode = this.startViewMode = 2;
97
+ break;
98
+ case 1:
99
+ case 'year':
100
+ this.viewMode = this.startViewMode = 1;
101
+ break;
102
+ case 0:
103
+ case 'month':
104
+ default:
105
+ this.viewMode = this.startViewMode = 0;
106
+ break;
107
+ }
108
+
109
+ this.todayBtn = (options.todayBtn||this.element.data('date-today-btn')||false);
110
+ this.todayHighlight = (options.todayHighlight||this.element.data('date-today-highlight')||false);
111
+
112
+ this.weekStart = ((options.weekStart||this.element.data('date-weekstart')||dates[this.language].weekStart||0) % 7);
113
+ this.weekEnd = ((this.weekStart + 6) % 7);
114
+ this.startDate = -Infinity;
115
+ this.endDate = Infinity;
116
+ this.setStartDate(options.startDate||this.element.data('date-startdate'));
117
+ this.setEndDate(options.endDate||this.element.data('date-enddate'));
53
118
  this.fillDow();
54
119
  this.fillMonths();
55
120
  this.update();
56
121
  this.showMode();
57
122
  };
58
-
123
+
59
124
  Datepicker.prototype = {
60
125
  constructor: Datepicker,
61
-
126
+
62
127
  show: function(e) {
63
128
  this.picker.show();
64
129
  this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
130
+ this.update();
65
131
  this.place();
66
132
  $(window).on('resize', $.proxy(this.place, this));
67
133
  if (e ) {
68
134
  e.stopPropagation();
69
135
  e.preventDefault();
70
136
  }
71
- if (!this.isInput) {
72
- $(document).on('mousedown', $.proxy(this.hide, this));
73
- }
74
137
  this.element.trigger({
75
138
  type: 'show',
76
139
  date: this.date
77
140
  });
78
141
  },
79
-
80
- hide: function(){
142
+
143
+ hide: function(e){
81
144
  this.picker.hide();
82
145
  $(window).off('resize', this.place);
83
- this.viewMode = 0;
146
+ this.viewMode = this.startViewMode;
84
147
  this.showMode();
85
148
  if (!this.isInput) {
86
149
  $(document).off('mousedown', this.hide);
87
150
  }
88
- this.setValue();
151
+ if (e && e.currentTarget.value)
152
+ this.setValue();
89
153
  this.element.trigger({
90
154
  type: 'hide',
91
155
  date: this.date
92
156
  });
93
157
  },
94
-
158
+
159
+ getDate: function() {
160
+ var d = this.getUTCDate();
161
+ return new Date(d.getTime() + (d.getTimezoneOffset()*60000))
162
+ },
163
+
164
+ getUTCDate: function() {
165
+ return this.date;
166
+ },
167
+
168
+ setDate: function(d) {
169
+ this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset()*60000)));
170
+ },
171
+
172
+ setUTCDate: function(d) {
173
+ this.date = d;
174
+ this.setValue();
175
+ },
176
+
95
177
  setValue: function() {
96
- var formated = DPGlobal.formatDate(this.date, this.format);
178
+ var formatted = DPGlobal.formatDate(this.date, this.format, this.language);
97
179
  if (!this.isInput) {
98
180
  if (this.component){
99
- this.element.find('input').prop('value', formated);
181
+ this.element.find('input').prop('value', formatted);
100
182
  }
101
- this.element.data('date', formated);
183
+ this.element.data('date', formatted);
102
184
  } else {
103
- this.element.prop('value', formated);
185
+ this.element.prop('value', formatted);
186
+ }
187
+ },
188
+
189
+ setStartDate: function(startDate){
190
+ this.startDate = startDate||-Infinity;
191
+ if (this.startDate !== -Infinity) {
192
+ this.startDate = DPGlobal.parseDate(this.startDate, this.format, this.language);
193
+ }
194
+ this.update();
195
+ this.updateNavArrows();
196
+ },
197
+
198
+ setEndDate: function(endDate){
199
+ this.endDate = endDate||Infinity;
200
+ if (this.endDate !== Infinity) {
201
+ this.endDate = DPGlobal.parseDate(this.endDate, this.format, this.language);
104
202
  }
203
+ this.update();
204
+ this.updateNavArrows();
105
205
  },
106
-
206
+
107
207
  place: function(){
208
+ var zIndex = parseInt(this.element.parents().filter(function() {
209
+ return $(this).css('z-index') != 'auto';
210
+ }).first().css('z-index'))+10;
108
211
  var offset = this.component ? this.component.offset() : this.element.offset();
109
212
  this.picker.css({
110
213
  top: offset.top + this.height,
111
- left: offset.left
214
+ left: offset.left,
215
+ zIndex: zIndex
112
216
  });
113
217
  },
114
-
218
+
115
219
  update: function(){
116
220
  this.date = DPGlobal.parseDate(
117
- this.isInput ? this.element.prop('value') : this.element.data('date'),
118
- this.format
221
+ this.isInput ? this.element.prop('value') : this.element.data('date') || this.element.find('input').prop('value'),
222
+ this.format, this.language
119
223
  );
120
- this.viewDate = new Date(this.date);
224
+ if (this.date < this.startDate) {
225
+ this.viewDate = new Date(this.startDate);
226
+ } else if (this.date > this.endDate) {
227
+ this.viewDate = new Date(this.endDate);
228
+ } else {
229
+ this.viewDate = new Date(this.date);
230
+ }
121
231
  this.fill();
122
232
  },
123
-
233
+
124
234
  fillDow: function(){
125
235
  var dowCnt = this.weekStart;
126
236
  var html = '<tr>';
127
237
  while (dowCnt < this.weekStart + 7) {
128
- html += '<th class="dow">'+DPGlobal.dates.daysMin[(dowCnt++)%7]+'</th>';
238
+ html += '<th class="dow">'+dates[this.language].daysMin[(dowCnt++)%7]+'</th>';
129
239
  }
130
240
  html += '</tr>';
131
241
  this.picker.find('.datepicker-days thead').append(html);
132
242
  },
133
-
243
+
134
244
  fillMonths: function(){
135
245
  var html = '';
136
246
  var i = 0
137
247
  while (i < 12) {
138
- html += '<span class="month">'+DPGlobal.dates.monthsShort[i++]+'</span>';
248
+ html += '<span class="month">'+dates[this.language].monthsShort[i++]+'</span>';
139
249
  }
140
- this.picker.find('.datepicker-months td').append(html);
250
+ this.picker.find('.datepicker-months td').html(html);
141
251
  },
142
-
252
+
143
253
  fill: function() {
144
254
  var d = new Date(this.viewDate),
145
- year = d.getFullYear(),
146
- month = d.getMonth(),
147
- currentDate = this.date.valueOf();
148
- this.picker.find('.datepicker-days th:eq(1)')
149
- .text(DPGlobal.dates.months[month]+' '+year);
150
- var prevMonth = new Date(year, month-1, 28,0,0,0,0),
151
- day = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth());
152
- prevMonth.setDate(day);
153
- prevMonth.setDate(day - (prevMonth.getDay() - this.weekStart + 7)%7);
255
+ year = d.getUTCFullYear(),
256
+ month = d.getUTCMonth(),
257
+ startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity,
258
+ startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity,
259
+ endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity,
260
+ endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity,
261
+ currentDate = this.date.valueOf(),
262
+ today = new Date();
263
+ this.picker.find('.datepicker-days thead th:eq(1)')
264
+ .text(dates[this.language].months[month]+' '+year);
265
+ this.picker.find('tfoot th.today')
266
+ .text(dates[this.language].today)
267
+ .toggle(this.todayBtn === 'linked');
268
+ this.updateNavArrows();
269
+ this.fillMonths();
270
+ var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
271
+ day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
272
+ prevMonth.setUTCDate(day);
273
+ prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7)%7);
154
274
  var nextMonth = new Date(prevMonth);
155
- nextMonth.setDate(nextMonth.getDate() + 42);
275
+ nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
156
276
  nextMonth = nextMonth.valueOf();
157
- html = [];
277
+ var html = [];
158
278
  var clsName;
159
279
  while(prevMonth.valueOf() < nextMonth) {
160
- if (prevMonth.getDay() == this.weekStart) {
280
+ if (prevMonth.getUTCDay() == this.weekStart) {
161
281
  html.push('<tr>');
162
282
  }
163
283
  clsName = '';
164
- if (prevMonth.getMonth() < month) {
284
+ if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) {
165
285
  clsName += ' old';
166
- } else if (prevMonth.getMonth() > month) {
286
+ } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) {
167
287
  clsName += ' new';
168
288
  }
289
+ // Compare internal UTC date with local today, not UTC today
290
+ if (this.todayHighlight &&
291
+ prevMonth.getUTCFullYear() == today.getFullYear() &&
292
+ prevMonth.getUTCMonth() == today.getMonth() &&
293
+ prevMonth.getUTCDate() == today.getDate()) {
294
+ clsName += ' today';
295
+ }
169
296
  if (prevMonth.valueOf() == currentDate) {
170
297
  clsName += ' active';
171
298
  }
172
- html.push('<td class="day'+clsName+'">'+prevMonth.getDate() + '</td>');
173
- if (prevMonth.getDay() == this.weekEnd) {
299
+ if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate) {
300
+ clsName += ' disabled';
301
+ }
302
+ html.push('<td class="day'+clsName+'">'+prevMonth.getUTCDate() + '</td>');
303
+ if (prevMonth.getUTCDay() == this.weekEnd) {
174
304
  html.push('</tr>');
175
305
  }
176
- prevMonth.setDate(prevMonth.getDate()+1);
306
+ prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
177
307
  }
178
308
  this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
179
- var currentYear = this.date.getFullYear();
180
-
309
+ var currentYear = this.date.getUTCFullYear();
310
+
181
311
  var months = this.picker.find('.datepicker-months')
182
312
  .find('th:eq(1)')
183
313
  .text(year)
184
314
  .end()
185
315
  .find('span').removeClass('active');
186
316
  if (currentYear == year) {
187
- months.eq(this.date.getMonth()).addClass('active');
317
+ months.eq(this.date.getUTCMonth()).addClass('active');
318
+ }
319
+ if (year < startYear || year > endYear) {
320
+ months.addClass('disabled');
321
+ }
322
+ if (year == startYear) {
323
+ months.slice(0, startMonth).addClass('disabled');
188
324
  }
189
-
325
+ if (year == endYear) {
326
+ months.slice(endMonth+1).addClass('disabled');
327
+ }
328
+
190
329
  html = '';
191
330
  year = parseInt(year/10, 10) * 10;
192
331
  var yearCont = this.picker.find('.datepicker-years')
@@ -196,12 +335,45 @@
196
335
  .find('td');
197
336
  year -= 1;
198
337
  for (var i = -1; i < 11; i++) {
199
- html += '<span class="year'+(i == -1 || i == 10 ? ' old' : '')+(currentYear == year ? ' active' : '')+'">'+year+'</span>';
338
+ html += '<span class="year'+(i == -1 || i == 10 ? ' old' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
200
339
  year += 1;
201
340
  }
202
341
  yearCont.html(html);
203
342
  },
204
-
343
+
344
+ updateNavArrows: function() {
345
+ var d = new Date(this.viewDate),
346
+ year = d.getUTCFullYear(),
347
+ month = d.getUTCMonth();
348
+ switch (this.viewMode) {
349
+ case 0:
350
+ if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) {
351
+ this.picker.find('.prev').css({visibility: 'hidden'});
352
+ } else {
353
+ this.picker.find('.prev').css({visibility: 'visible'});
354
+ }
355
+ if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) {
356
+ this.picker.find('.next').css({visibility: 'hidden'});
357
+ } else {
358
+ this.picker.find('.next').css({visibility: 'visible'});
359
+ }
360
+ break;
361
+ case 1:
362
+ case 2:
363
+ if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) {
364
+ this.picker.find('.prev').css({visibility: 'hidden'});
365
+ } else {
366
+ this.picker.find('.prev').css({visibility: 'visible'});
367
+ }
368
+ if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) {
369
+ this.picker.find('.next').css({visibility: 'hidden'});
370
+ } else {
371
+ this.picker.find('.next').css({visibility: 'visible'});
372
+ }
373
+ break;
374
+ }
375
+ },
376
+
205
377
  click: function(e) {
206
378
  e.stopPropagation();
207
379
  e.preventDefault();
@@ -215,64 +387,254 @@
215
387
  break;
216
388
  case 'prev':
217
389
  case 'next':
218
- this.viewDate['set'+DPGlobal.modes[this.viewMode].navFnc].call(
219
- this.viewDate,
220
- this.viewDate['get'+DPGlobal.modes[this.viewMode].navFnc].call(this.viewDate) +
221
- DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1)
222
- );
390
+ var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);
391
+ switch(this.viewMode){
392
+ case 0:
393
+ this.viewDate = this.moveMonth(this.viewDate, dir);
394
+ break;
395
+ case 1:
396
+ case 2:
397
+ this.viewDate = this.moveYear(this.viewDate, dir);
398
+ break;
399
+ }
223
400
  this.fill();
224
401
  break;
402
+ case 'today':
403
+ var date = new Date();
404
+ date.setUTCHours(0);
405
+ date.setUTCMinutes(0);
406
+ date.setUTCSeconds(0);
407
+ date.setUTCMilliseconds(0);
408
+
409
+ this.showMode(-2);
410
+ var which = this.todayBtn == 'linked' ? null : 'view';
411
+ this._setDate(date, which);
412
+ break;
225
413
  }
226
414
  break;
227
415
  case 'span':
228
- if (target.is('.month')) {
229
- var month = target.parent().find('span').index(target);
230
- this.viewDate.setMonth(month);
231
- } else {
232
- var year = parseInt(target.text(), 10)||0;
233
- this.viewDate.setFullYear(year);
416
+ if (!target.is('.disabled')) {
417
+ this.viewDate.setUTCDate(1);
418
+ if (target.is('.month')) {
419
+ var month = target.parent().find('span').index(target);
420
+ this.viewDate.setUTCMonth(month);
421
+ this.element.trigger({
422
+ type: 'changeMonth',
423
+ date: this.viewDate
424
+ });
425
+ } else {
426
+ var year = parseInt(target.text(), 10)||0;
427
+ this.viewDate.setUTCFullYear(year);
428
+ this.element.trigger({
429
+ type: 'changeYear',
430
+ date: this.viewDate
431
+ });
432
+ }
433
+ this.showMode(-1);
434
+ this.fill();
234
435
  }
235
- this.showMode(-1);
236
- this.fill();
237
436
  break;
238
437
  case 'td':
239
- if (target.is('.day')){
438
+ if (target.is('.day') && !target.is('.disabled')){
240
439
  var day = parseInt(target.text(), 10)||1;
241
- var month = this.viewDate.getMonth();
440
+ var year = this.viewDate.getUTCFullYear(),
441
+ month = this.viewDate.getUTCMonth();
242
442
  if (target.is('.old')) {
243
- month -= 1;
443
+ if (month == 0) {
444
+ month = 11;
445
+ year -= 1;
446
+ } else {
447
+ month -= 1;
448
+ }
244
449
  } else if (target.is('.new')) {
245
- month += 1;
450
+ if (month == 11) {
451
+ month = 0;
452
+ year += 1;
453
+ } else {
454
+ month += 1;
455
+ }
246
456
  }
247
- var year = this.viewDate.getFullYear();
248
- this.date = new Date(year, month, day,0,0,0,0);
249
- this.viewDate = new Date(year, month, day,0,0,0,0);
250
- this.fill();
251
- this.setValue();
252
- this.element.trigger({
253
- type: 'changeDate',
254
- date: this.date
255
- });
457
+ this._setDate(UTCDate(year, month, day,0,0,0,0));
256
458
  }
257
459
  break;
258
460
  }
259
461
  }
260
462
  },
261
-
262
- mousedown: function(e){
263
- e.stopPropagation();
264
- e.preventDefault();
463
+
464
+ _setDate: function(date, which){
465
+ if (!which || which == 'date')
466
+ this.date = date;
467
+ if (!which || which == 'view')
468
+ this.viewDate = date;
469
+ this.fill();
470
+ this.setValue();
471
+ this.element.trigger({
472
+ type: 'changeDate',
473
+ date: this.date
474
+ });
475
+ var element;
476
+ if (this.isInput) {
477
+ element = this.element;
478
+ } else if (this.component){
479
+ element = this.element.find('input');
480
+ }
481
+ if (element) {
482
+ element.change();
483
+ if (this.autoclose) {
484
+ this.hide();
485
+ }
486
+ }
487
+ },
488
+
489
+ moveMonth: function(date, dir){
490
+ if (!dir) return date;
491
+ var new_date = new Date(date.valueOf()),
492
+ day = new_date.getUTCDate(),
493
+ month = new_date.getUTCMonth(),
494
+ mag = Math.abs(dir),
495
+ new_month, test;
496
+ dir = dir > 0 ? 1 : -1;
497
+ if (mag == 1){
498
+ test = dir == -1
499
+ // If going back one month, make sure month is not current month
500
+ // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
501
+ ? function(){ return new_date.getUTCMonth() == month; }
502
+ // If going forward one month, make sure month is as expected
503
+ // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
504
+ : function(){ return new_date.getUTCMonth() != new_month; };
505
+ new_month = month + dir;
506
+ new_date.setUTCMonth(new_month);
507
+ // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
508
+ if (new_month < 0 || new_month > 11)
509
+ new_month = (new_month + 12) % 12;
510
+ } else {
511
+ // For magnitudes >1, move one month at a time...
512
+ for (var i=0; i<mag; i++)
513
+ // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
514
+ new_date = this.moveMonth(new_date, dir);
515
+ // ...then reset the day, keeping it in the new month
516
+ new_month = new_date.getUTCMonth();
517
+ new_date.setUTCDate(day);
518
+ test = function(){ return new_month != new_date.getUTCMonth(); };
519
+ }
520
+ // Common date-resetting loop -- if date is beyond end of month, make it
521
+ // end of month
522
+ while (test()){
523
+ new_date.setUTCDate(--day);
524
+ new_date.setUTCMonth(new_month);
525
+ }
526
+ return new_date;
527
+ },
528
+
529
+ moveYear: function(date, dir){
530
+ return this.moveMonth(date, dir*12);
265
531
  },
266
-
532
+
533
+ dateWithinRange: function(date){
534
+ return date >= this.startDate && date <= this.endDate;
535
+ },
536
+
537
+ keydown: function(e){
538
+ if (this.picker.is(':not(:visible)')){
539
+ if (e.keyCode == 27) // allow escape to hide and re-show picker
540
+ this.show();
541
+ return;
542
+ }
543
+ var dateChanged = false,
544
+ dir, day, month,
545
+ newDate, newViewDate;
546
+ switch(e.keyCode){
547
+ case 27: // escape
548
+ this.hide();
549
+ e.preventDefault();
550
+ break;
551
+ case 37: // left
552
+ case 39: // right
553
+ if (!this.keyboardNavigation) break;
554
+ dir = e.keyCode == 37 ? -1 : 1;
555
+ if (e.ctrlKey){
556
+ newDate = this.moveYear(this.date, dir);
557
+ newViewDate = this.moveYear(this.viewDate, dir);
558
+ } else if (e.shiftKey){
559
+ newDate = this.moveMonth(this.date, dir);
560
+ newViewDate = this.moveMonth(this.viewDate, dir);
561
+ } else {
562
+ newDate = new Date(this.date);
563
+ newDate.setUTCDate(this.date.getUTCDate() + dir);
564
+ newViewDate = new Date(this.viewDate);
565
+ newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir);
566
+ }
567
+ if (this.dateWithinRange(newDate)){
568
+ this.date = newDate;
569
+ this.viewDate = newViewDate;
570
+ this.setValue();
571
+ this.update();
572
+ e.preventDefault();
573
+ dateChanged = true;
574
+ }
575
+ break;
576
+ case 38: // up
577
+ case 40: // down
578
+ if (!this.keyboardNavigation) break;
579
+ dir = e.keyCode == 38 ? -1 : 1;
580
+ if (e.ctrlKey){
581
+ newDate = this.moveYear(this.date, dir);
582
+ newViewDate = this.moveYear(this.viewDate, dir);
583
+ } else if (e.shiftKey){
584
+ newDate = this.moveMonth(this.date, dir);
585
+ newViewDate = this.moveMonth(this.viewDate, dir);
586
+ } else {
587
+ newDate = new Date(this.date);
588
+ newDate.setUTCDate(this.date.getUTCDate() + dir * 7);
589
+ newViewDate = new Date(this.viewDate);
590
+ newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7);
591
+ }
592
+ if (this.dateWithinRange(newDate)){
593
+ this.date = newDate;
594
+ this.viewDate = newViewDate;
595
+ this.setValue();
596
+ this.update();
597
+ e.preventDefault();
598
+ dateChanged = true;
599
+ }
600
+ break;
601
+ case 13: // enter
602
+ this.hide();
603
+ e.preventDefault();
604
+ break;
605
+ case 9: // tab
606
+ this.hide();
607
+ break;
608
+ }
609
+ if (dateChanged){
610
+ this.element.trigger({
611
+ type: 'changeDate',
612
+ date: this.date
613
+ });
614
+ var element;
615
+ if (this.isInput) {
616
+ element = this.element;
617
+ } else if (this.component){
618
+ element = this.element.find('input');
619
+ }
620
+ if (element) {
621
+ element.change();
622
+ }
623
+ }
624
+ },
625
+
267
626
  showMode: function(dir) {
268
627
  if (dir) {
269
628
  this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir));
270
629
  }
271
630
  this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
631
+ this.updateNavArrows();
272
632
  }
273
633
  };
274
-
634
+
275
635
  $.fn.datepicker = function ( option ) {
636
+ var args = Array.apply(null, arguments);
637
+ args.shift();
276
638
  return this.each(function () {
277
639
  var $this = $(this),
278
640
  data = $this.data('datepicker'),
@@ -280,14 +642,26 @@
280
642
  if (!data) {
281
643
  $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
282
644
  }
283
- if (typeof option == 'string') data[option]();
645
+ if (typeof option == 'string' && typeof data[option] == 'function') {
646
+ data[option].apply(data, args);
647
+ }
284
648
  });
285
649
  };
286
650
 
287
651
  $.fn.datepicker.defaults = {
288
652
  };
289
653
  $.fn.datepicker.Constructor = Datepicker;
290
-
654
+ var dates = $.fn.datepicker.dates = {
655
+ en: {
656
+ days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
657
+ daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
658
+ daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
659
+ months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
660
+ monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
661
+ today: "Today"
662
+ }
663
+ }
664
+
291
665
  var DPGlobal = {
292
666
  modes: [
293
667
  {
@@ -305,68 +679,126 @@
305
679
  navFnc: 'FullYear',
306
680
  navStep: 10
307
681
  }],
308
- dates:{
309
- days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
310
- daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
311
- daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
312
- months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
313
- monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
314
- },
315
682
  isLeapYear: function (year) {
316
683
  return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
317
684
  },
318
685
  getDaysInMonth: function (year, month) {
319
686
  return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
320
687
  },
688
+ validParts: /dd?|mm?|MM?|yy(?:yy)?/g,
689
+ nonpunctuation: /[^ -\/:-@\[-`{-~\t\n\r]+/g,
321
690
  parseFormat: function(format){
322
- var separator = format.match(/[.\/-].*?/),
323
- parts = format.split(/\W+/);
324
- if (!separator || !parts || parts.length == 0){
691
+ // IE treats \0 as a string end in inputs (truncating the value),
692
+ // so it's a bad format delimiter, anyway
693
+ var separators = format.replace(this.validParts, '\0').split('\0'),
694
+ parts = format.match(this.validParts);
695
+ if (!separators || !separators.length || !parts || parts.length == 0){
325
696
  throw new Error("Invalid date format.");
326
697
  }
327
- return {separator: separator, parts: parts};
698
+ return {separators: separators, parts: parts};
328
699
  },
329
- parseDate: function(date, format) {
330
- var parts = date.split(format.separator),
331
- date = new Date(1970, 1, 1, 0, 0, 0),
332
- val;
333
- if (parts.length == format.parts.length) {
334
- for (var i=0, cnt = format.parts.length; i < cnt; i++) {
335
- val = parseInt(parts[i], 10)||1;
336
- switch(format.parts[i]) {
337
- case 'dd':
700
+ parseDate: function(date, format, language) {
701
+ if (date instanceof Date) return date;
702
+ if (/^[-+]\d+[dmwy]([\s,]+[-+]\d+[dmwy])*$/.test(date)) {
703
+ var part_re = /([-+]\d+)([dmwy])/,
704
+ parts = date.match(/([-+]\d+)([dmwy])/g),
705
+ part, dir;
706
+ date = new Date();
707
+ for (var i=0; i<parts.length; i++) {
708
+ part = part_re.exec(parts[i]);
709
+ dir = parseInt(part[1]);
710
+ switch(part[2]){
338
711
  case 'd':
339
- date.setDate(val);
712
+ date.setUTCDate(date.getUTCDate() + dir);
340
713
  break;
341
- case 'mm':
342
714
  case 'm':
343
- date.setMonth(val - 1);
715
+ date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);
344
716
  break;
345
- case 'yy':
346
- date.setFullYear(2000 + val);
717
+ case 'w':
718
+ date.setUTCDate(date.getUTCDate() + dir * 7);
347
719
  break;
348
- case 'yyyy':
349
- date.setFullYear(val);
720
+ case 'y':
721
+ date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);
350
722
  break;
351
723
  }
352
724
  }
725
+ return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
726
+ }
727
+ var parts = date && date.match(this.nonpunctuation) || [],
728
+ date = new Date(),
729
+ parsed = {},
730
+ setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
731
+ setters_map = {
732
+ yyyy: function(d,v){ return d.setUTCFullYear(v); },
733
+ yy: function(d,v){ return d.setUTCFullYear(2000+v); },
734
+ m: function(d,v){
735
+ v -= 1;
736
+ while (v<0) v += 12;
737
+ v %= 12;
738
+ d.setUTCMonth(v);
739
+ while (d.getUTCMonth() != v)
740
+ d.setUTCDate(d.getUTCDate()-1);
741
+ return d;
742
+ },
743
+ d: function(d,v){ return d.setUTCDate(v); }
744
+ },
745
+ val, filtered, part;
746
+ setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
747
+ setters_map['dd'] = setters_map['d'];
748
+ date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
749
+ if (parts.length == format.parts.length) {
750
+ for (var i=0, cnt = format.parts.length; i < cnt; i++) {
751
+ val = parseInt(parts[i], 10);
752
+ part = format.parts[i];
753
+ if (isNaN(val)) {
754
+ switch(part) {
755
+ case 'MM':
756
+ filtered = $(dates[language].months).filter(function(){
757
+ var m = this.slice(0, parts[i].length),
758
+ p = parts[i].slice(0, m.length);
759
+ return m == p;
760
+ });
761
+ val = $.inArray(filtered[0], dates[language].months) + 1;
762
+ break;
763
+ case 'M':
764
+ filtered = $(dates[language].monthsShort).filter(function(){
765
+ var m = this.slice(0, parts[i].length),
766
+ p = parts[i].slice(0, m.length);
767
+ return m == p;
768
+ });
769
+ val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
770
+ break;
771
+ }
772
+ }
773
+ parsed[part] = val;
774
+ }
775
+ for (var i=0, s; i<setters_order.length; i++){
776
+ s = setters_order[i];
777
+ if (s in parsed && !isNaN(parsed[s]))
778
+ setters_map[s](date, parsed[s])
779
+ }
353
780
  }
354
781
  return date;
355
782
  },
356
- formatDate: function(date, format){
783
+ formatDate: function(date, format, language){
357
784
  var val = {
358
- d: date.getDate(),
359
- m: date.getMonth() + 1,
360
- yy: date.getFullYear().toString().substring(2),
361
- yyyy: date.getFullYear()
785
+ d: date.getUTCDate(),
786
+ m: date.getUTCMonth() + 1,
787
+ M: dates[language].monthsShort[date.getUTCMonth()],
788
+ MM: dates[language].months[date.getUTCMonth()],
789
+ yy: date.getUTCFullYear().toString().substring(2),
790
+ yyyy: date.getUTCFullYear()
362
791
  };
363
792
  val.dd = (val.d < 10 ? '0' : '') + val.d;
364
793
  val.mm = (val.m < 10 ? '0' : '') + val.m;
365
- var date = [];
794
+ var date = [],
795
+ seps = $.extend([], format.separators);
366
796
  for (var i=0, cnt = format.parts.length; i < cnt; i++) {
797
+ if (seps.length)
798
+ date.push(seps.shift())
367
799
  date.push(val[format.parts[i]]);
368
800
  }
369
- return date.join(format.separator);
801
+ return date.join('');
370
802
  },
371
803
  headTemplate: '<thead>'+
372
804
  '<tr>'+
@@ -375,27 +807,30 @@
375
807
  '<th class="next"><i class="icon-arrow-right"/></th>'+
376
808
  '</tr>'+
377
809
  '</thead>',
378
- contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>'
810
+ contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
811
+ footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr></tfoot>'
379
812
  };
380
813
  DPGlobal.template = '<div class="datepicker dropdown-menu">'+
381
814
  '<div class="datepicker-days">'+
382
815
  '<table class=" table-condensed">'+
383
816
  DPGlobal.headTemplate+
384
817
  '<tbody></tbody>'+
818
+ DPGlobal.footTemplate+
385
819
  '</table>'+
386
820
  '</div>'+
387
821
  '<div class="datepicker-months">'+
388
822
  '<table class="table-condensed">'+
389
823
  DPGlobal.headTemplate+
390
824
  DPGlobal.contTemplate+
825
+ DPGlobal.footTemplate+
391
826
  '</table>'+
392
827
  '</div>'+
393
828
  '<div class="datepicker-years">'+
394
829
  '<table class="table-condensed">'+
395
830
  DPGlobal.headTemplate+
396
831
  DPGlobal.contTemplate+
832
+ DPGlobal.footTemplate+
397
833
  '</table>'+
398
834
  '</div>'+
399
835
  '</div>';
400
-
401
- }( window.jQuery )
836
+ }( window.jQuery );