taskwarrior-web 1.0.8 → 1.0.9

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 (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 );