major updates to slice reservation page and plugins
[myslice.git] / third-party / bootstrap-datepicker-1 / bootstrap-datepicker.js
1 /* =========================================================
2  * bootstrap-datepicker.js
3  * http://www.eyecon.ro/bootstrap-datepicker
4  * =========================================================
5  * Copyright 2012 Stefan Petre
6  * Improvements by Andrew Rowls
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ========================================================= */
20
21 !function( $ ) {
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
31         // Picker object
32
33         var Datepicker = function(element, options) {
34                 var that = this;
35
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";
39                 this.isRTL = dates[this.language].rtl||false;
40                 this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
41                 this.isInline = false;
42                 this.isInput = this.element.is('input');
43                 this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
44                 this.hasInput = this.component && this.element.find('input').length;
45                 if(this.component && this.component.length === 0)
46                         this.component = false;
47
48                 this._attachEvents();
49
50                 this.forceParse = true;
51                 if ('forceParse' in options) {
52                         this.forceParse = options.forceParse;
53                 } else if ('dateForceParse' in this.element.data()) {
54                         this.forceParse = this.element.data('date-force-parse');
55                 }
56                  
57
58                 this.picker = $(DPGlobal.template)
59                                                         .appendTo(this.isInline ? this.element : 'body')
60                                                         .on({
61                                                                 click: $.proxy(this.click, this),
62                                                                 mousedown: $.proxy(this.mousedown, this)
63                                                         });
64
65                 if(this.isInline) {
66                         this.picker.addClass('datepicker-inline');
67                 } else {
68                         this.picker.addClass('datepicker-dropdown dropdown-menu');
69                 }
70                 if (this.isRTL){
71                         this.picker.addClass('datepicker-rtl');
72                         this.picker.find('.prev i, .next i')
73                                                 .toggleClass('icon-arrow-left icon-arrow-right');
74                 }
75                 $(document).on('mousedown', function (e) {
76                         // Clicked outside the datepicker, hide it
77                         if ($(e.target).closest('.datepicker').length === 0) {
78                                 that.hide();
79                         }
80                 });
81
82                 this.autoclose = false;
83                 if ('autoclose' in options) {
84                         this.autoclose = options.autoclose;
85                 } else if ('dateAutoclose' in this.element.data()) {
86                         this.autoclose = this.element.data('date-autoclose');
87                 }
88
89                 this.keyboardNavigation = true;
90                 if ('keyboardNavigation' in options) {
91                         this.keyboardNavigation = options.keyboardNavigation;
92                 } else if ('dateKeyboardNavigation' in this.element.data()) {
93                         this.keyboardNavigation = this.element.data('date-keyboard-navigation');
94                 }
95
96                 this.viewMode = this.startViewMode = 0;
97                 switch(options.startView || this.element.data('date-start-view')){
98                         case 2:
99                         case 'decade':
100                                 this.viewMode = this.startViewMode = 2;
101                                 break;
102                         case 1:
103                         case 'year':
104                                 this.viewMode = this.startViewMode = 1;
105                                 break;
106                 }
107
108                 this.todayBtn = (options.todayBtn||this.element.data('date-today-btn')||false);
109                 this.todayHighlight = (options.todayHighlight||this.element.data('date-today-highlight')||false);
110
111                 this.weekStart = ((options.weekStart||this.element.data('date-weekstart')||dates[this.language].weekStart||0) % 7);
112                 this.weekEnd = ((this.weekStart + 6) % 7);
113                 this.startDate = -Infinity;
114                 this.endDate = Infinity;
115                 this.daysOfWeekDisabled = [];
116                 this.setStartDate(options.startDate||this.element.data('date-startdate'));
117                 this.setEndDate(options.endDate||this.element.data('date-enddate'));
118                 this.setDaysOfWeekDisabled(options.daysOfWeekDisabled||this.element.data('date-days-of-week-disabled'));
119                 this.fillDow();
120                 this.fillMonths();
121                 this.update();
122                 this.showMode();
123
124                 if(this.isInline) {
125                         this.show();
126                 }
127         };
128
129         Datepicker.prototype = {
130                 constructor: Datepicker,
131
132                 _events: [],
133                 _attachEvents: function(){
134                         this._detachEvents();
135                         if (this.isInput) { // single input
136                                 this._events = [
137                                         [this.element, {
138                                                 focus: $.proxy(this.show, this),
139                                                 keyup: $.proxy(this.update, this),
140                                                 keydown: $.proxy(this.keydown, this)
141                                         }]
142                                 ];
143                         }
144                         else if (this.component && this.hasInput){ // component: input + button
145                                 this._events = [
146                                         // For components that are not readonly, allow keyboard nav
147                                         [this.element.find('input'), {
148                                                 focus: $.proxy(this.show, this),
149                                                 keyup: $.proxy(this.update, this),
150                                                 keydown: $.proxy(this.keydown, this)
151                                         }],
152                                         [this.component, {
153                                                 click: $.proxy(this.show, this)
154                                         }]
155                                 ];
156                         }
157                                                 else if (this.element.is('div')) {  // inline datepicker
158                                                         this.isInline = true;
159                                                 }
160                         else {
161                                 this._events = [
162                                         [this.element, {
163                                                 click: $.proxy(this.show, this)
164                                         }]
165                                 ];
166                         }
167                         for (var i=0, el, ev; i<this._events.length; i++){
168                                 el = this._events[i][0];
169                                 ev = this._events[i][1];
170                                 el.on(ev);
171                         }
172                 },
173                 _detachEvents: function(){
174                         for (var i=0, el, ev; i<this._events.length; i++){
175                                 el = this._events[i][0];
176                                 ev = this._events[i][1];
177                                 el.off(ev);
178                         }
179                         this._events = [];
180                 },
181
182                 show: function(e) {
183                         this.picker.show();
184                         this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
185                         this.update();
186                         this.place();
187                         $(window).on('resize', $.proxy(this.place, this));
188                         if (e ) {
189                                 e.stopPropagation();
190                                 e.preventDefault();
191                         }
192                         this.element.trigger({
193                                 type: 'show',
194                                 date: this.date
195                         });
196                 },
197
198                 hide: function(e){
199                         if(this.isInline) return;
200                         this.picker.hide();
201                         $(window).off('resize', this.place);
202                         this.viewMode = this.startViewMode;
203                         this.showMode();
204                         if (!this.isInput) {
205                                 $(document).off('mousedown', this.hide);
206                         }
207
208                         if (
209                                 this.forceParse &&
210                                 (
211                                         this.isInput && this.element.val() ||
212                                         this.hasInput && this.element.find('input').val()
213                                 )
214                         )
215                                 this.setValue();
216                         this.element.trigger({
217                                 type: 'hide',
218                                 date: this.date
219                         });
220                 },
221
222                 remove: function() {
223                         this._detachEvents();
224                         this.picker.remove();
225                         delete this.element.data().datepicker;
226                 },
227
228                 getDate: function() {
229                         var d = this.getUTCDate();
230                         return new Date(d.getTime() + (d.getTimezoneOffset()*60000));
231                 },
232
233                 getUTCDate: function() {
234                         return this.date;
235                 },
236
237                 setDate: function(d) {
238                         this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset()*60000)));
239                 },
240
241                 setUTCDate: function(d) {
242                         this.date = d;
243                         this.setValue();
244                 },
245
246                 setValue: function() {
247                         var formatted = this.getFormattedDate();
248                         if (!this.isInput) {
249                                 if (this.component){
250                                         this.element.find('input').val(formatted);
251                                 }
252                                 this.element.data('date', formatted);
253                         } else {
254                                 this.element.val(formatted);
255                         }
256                 },
257
258                 getFormattedDate: function(format) {
259                         if (format === undefined)
260                                 format = this.format;
261                         return DPGlobal.formatDate(this.date, format, this.language);
262                 },
263
264                 setStartDate: function(startDate){
265                         this.startDate = startDate||-Infinity;
266                         if (this.startDate !== -Infinity) {
267                                 this.startDate = DPGlobal.parseDate(this.startDate, this.format, this.language);
268                         }
269                         this.update();
270                         this.updateNavArrows();
271                 },
272
273                 setEndDate: function(endDate){
274                         this.endDate = endDate||Infinity;
275                         if (this.endDate !== Infinity) {
276                                 this.endDate = DPGlobal.parseDate(this.endDate, this.format, this.language);
277                         }
278                         this.update();
279                         this.updateNavArrows();
280                 },
281
282                 setDaysOfWeekDisabled: function(daysOfWeekDisabled){
283                         this.daysOfWeekDisabled = daysOfWeekDisabled||[];
284                         if (!$.isArray(this.daysOfWeekDisabled)) {
285                                 this.daysOfWeekDisabled = this.daysOfWeekDisabled.split(/,\s*/);
286                         }
287                         this.daysOfWeekDisabled = $.map(this.daysOfWeekDisabled, function (d) {
288                                 return parseInt(d, 10);
289                         });
290                         this.update();
291                         this.updateNavArrows();
292                 },
293
294                 place: function(){
295                                                 if(this.isInline) return;
296                         var zIndex = parseInt(this.element.parents().filter(function() {
297                                                         return $(this).css('z-index') != 'auto';
298                                                 }).first().css('z-index'))+10;
299                         var offset = this.component ? this.component.offset() : this.element.offset();
300                         var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(true);
301                         this.picker.css({
302                                 top: offset.top + height,
303                                 left: offset.left,
304                                 zIndex: zIndex
305                         });
306                 },
307
308                 update: function(){
309                         var date, fromArgs = false;
310                         if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {
311                                 date = arguments[0];
312                                 fromArgs = true;
313                         } else {
314                                 date = this.isInput ? this.element.val() : this.element.data('date') || this.element.find('input').val();
315                         }
316
317                         this.date = DPGlobal.parseDate(date, this.format, this.language);
318
319                         if(fromArgs) this.setValue();
320
321                         var oldViewDate = this.viewDate;
322                         if (this.date < this.startDate) {
323                                 this.viewDate = new Date(this.startDate);
324                         } else if (this.date > this.endDate) {
325                                 this.viewDate = new Date(this.endDate);
326                         } else {
327                                 this.viewDate = new Date(this.date);
328                         }
329
330                         if (oldViewDate && oldViewDate.getTime() != this.viewDate.getTime()){
331                                 this.element.trigger({
332                                         type: 'changeDate',
333                                         date: this.viewDate
334                                 });
335                         }
336                         this.fill();
337                 },
338
339                 fillDow: function(){
340                         var dowCnt = this.weekStart,
341                         html = '<tr>';
342                         while (dowCnt < this.weekStart + 7) {
343                                 html += '<th class="dow">'+dates[this.language].daysMin[(dowCnt++)%7]+'</th>';
344                         }
345                         html += '</tr>';
346                         this.picker.find('.datepicker-days thead').append(html);
347                 },
348
349                 fillMonths: function(){
350                         var html = '',
351                         i = 0;
352                         while (i < 12) {
353                                 html += '<span class="month">'+dates[this.language].monthsShort[i++]+'</span>';
354                         }
355                         this.picker.find('.datepicker-months td').html(html);
356                 },
357
358                 fill: function() {
359                         var d = new Date(this.viewDate),
360                                 year = d.getUTCFullYear(),
361                                 month = d.getUTCMonth(),
362                                 startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity,
363                                 startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity,
364                                 endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity,
365                                 endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity,
366                                 currentDate = this.date && this.date.valueOf(),
367                                 today = new Date();
368                         this.picker.find('.datepicker-days thead th:eq(1)')
369                                                 .text(dates[this.language].months[month]+' '+year);
370                         this.picker.find('tfoot th.today')
371                                                 .text(dates[this.language].today)
372                                                 .toggle(this.todayBtn !== false);
373                         this.updateNavArrows();
374                         this.fillMonths();
375                         var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
376                                 day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
377                         prevMonth.setUTCDate(day);
378                         prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7)%7);
379                         var nextMonth = new Date(prevMonth);
380                         nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
381                         nextMonth = nextMonth.valueOf();
382                         var html = [];
383                         var clsName;
384                         while(prevMonth.valueOf() < nextMonth) {
385                                 if (prevMonth.getUTCDay() == this.weekStart) {
386                                         html.push('<tr>');
387                                 }
388                                 clsName = '';
389                                 if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) {
390                                         clsName += ' old';
391                                 } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) {
392                                         clsName += ' new';
393                                 }
394                                 // Compare internal UTC date with local today, not UTC today
395                                 if (this.todayHighlight &&
396                                         prevMonth.getUTCFullYear() == today.getFullYear() &&
397                                         prevMonth.getUTCMonth() == today.getMonth() &&
398                                         prevMonth.getUTCDate() == today.getDate()) {
399                                         clsName += ' today';
400                                 }
401                                 if (currentDate && prevMonth.valueOf() == currentDate) {
402                                         clsName += ' active';
403                                 }
404                                 if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate ||
405                                         $.inArray(prevMonth.getUTCDay(), this.daysOfWeekDisabled) !== -1) {
406                                         clsName += ' disabled';
407                                 }
408                                 html.push('<td class="day'+clsName+'">'+prevMonth.getUTCDate() + '</td>');
409                                 if (prevMonth.getUTCDay() == this.weekEnd) {
410                                         html.push('</tr>');
411                                 }
412                                 prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
413                         }
414                         this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
415                         var currentYear = this.date && this.date.getUTCFullYear();
416
417                         var months = this.picker.find('.datepicker-months')
418                                                 .find('th:eq(1)')
419                                                         .text(year)
420                                                         .end()
421                                                 .find('span').removeClass('active');
422                         if (currentYear && currentYear == year) {
423                                 months.eq(this.date.getUTCMonth()).addClass('active');
424                         }
425                         if (year < startYear || year > endYear) {
426                                 months.addClass('disabled');
427                         }
428                         if (year == startYear) {
429                                 months.slice(0, startMonth).addClass('disabled');
430                         }
431                         if (year == endYear) {
432                                 months.slice(endMonth+1).addClass('disabled');
433                         }
434
435                         html = '';
436                         year = parseInt(year/10, 10) * 10;
437                         var yearCont = this.picker.find('.datepicker-years')
438                                                                 .find('th:eq(1)')
439                                                                         .text(year + '-' + (year + 9))
440                                                                         .end()
441                                                                 .find('td');
442                         year -= 1;
443                         for (var i = -1; i < 11; i++) {
444                                 html += '<span class="year'+(i == -1 || i == 10 ? ' old' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
445                                 year += 1;
446                         }
447                         yearCont.html(html);
448                 },
449
450                 updateNavArrows: function() {
451                         var d = new Date(this.viewDate),
452                                 year = d.getUTCFullYear(),
453                                 month = d.getUTCMonth();
454                         switch (this.viewMode) {
455                                 case 0:
456                                         if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) {
457                                                 this.picker.find('.prev').css({visibility: 'hidden'});
458                                         } else {
459                                                 this.picker.find('.prev').css({visibility: 'visible'});
460                                         }
461                                         if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) {
462                                                 this.picker.find('.next').css({visibility: 'hidden'});
463                                         } else {
464                                                 this.picker.find('.next').css({visibility: 'visible'});
465                                         }
466                                         break;
467                                 case 1:
468                                 case 2:
469                                         if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) {
470                                                 this.picker.find('.prev').css({visibility: 'hidden'});
471                                         } else {
472                                                 this.picker.find('.prev').css({visibility: 'visible'});
473                                         }
474                                         if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) {
475                                                 this.picker.find('.next').css({visibility: 'hidden'});
476                                         } else {
477                                                 this.picker.find('.next').css({visibility: 'visible'});
478                                         }
479                                         break;
480                         }
481                 },
482
483                 click: function(e) {
484                         e.stopPropagation();
485                         e.preventDefault();
486                         var target = $(e.target).closest('span, td, th');
487                         if (target.length == 1) {
488                                 switch(target[0].nodeName.toLowerCase()) {
489                                         case 'th':
490                                                 switch(target[0].className) {
491                                                         case 'switch':
492                                                                 this.showMode(1);
493                                                                 break;
494                                                         case 'prev':
495                                                         case 'next':
496                                                                 var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);
497                                                                 switch(this.viewMode){
498                                                                         case 0:
499                                                                                 this.viewDate = this.moveMonth(this.viewDate, dir);
500                                                                                 break;
501                                                                         case 1:
502                                                                         case 2:
503                                                                                 this.viewDate = this.moveYear(this.viewDate, dir);
504                                                                                 break;
505                                                                 }
506                                                                 this.fill();
507                                                                 break;
508                                                         case 'today':
509                                                                 var date = new Date();
510                                                                 date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
511
512                                                                 this.showMode(-2);
513                                                                 var which = this.todayBtn == 'linked' ? null : 'view';
514                                                                 this._setDate(date, which);
515                                                                 break;
516                                                 }
517                                                 break;
518                                         case 'span':
519                                                 if (!target.is('.disabled')) {
520                                                         this.viewDate.setUTCDate(1);
521                                                         if (target.is('.month')) {
522                                                                 var month = target.parent().find('span').index(target);
523                                                                 this.viewDate.setUTCMonth(month);
524                                                                 this.element.trigger({
525                                                                         type: 'changeMonth',
526                                                                         date: this.viewDate
527                                                                 });
528                                                         } else {
529                                                                 var year = parseInt(target.text(), 10)||0;
530                                                                 this.viewDate.setUTCFullYear(year);
531                                                                 this.element.trigger({
532                                                                         type: 'changeYear',
533                                                                         date: this.viewDate
534                                                                 });
535                                                         }
536                                                         this.showMode(-1);
537                                                         this.fill();
538                                                 }
539                                                 break;
540                                         case 'td':
541                                                 if (target.is('.day') && !target.is('.disabled')){
542                                                         var day = parseInt(target.text(), 10)||1;
543                                                         var year = this.viewDate.getUTCFullYear(),
544                                                                 month = this.viewDate.getUTCMonth();
545                                                         if (target.is('.old')) {
546                                                                 if (month === 0) {
547                                                                         month = 11;
548                                                                         year -= 1;
549                                                                 } else {
550                                                                         month -= 1;
551                                                                 }
552                                                         } else if (target.is('.new')) {
553                                                                 if (month == 11) {
554                                                                         month = 0;
555                                                                         year += 1;
556                                                                 } else {
557                                                                         month += 1;
558                                                                 }
559                                                         }
560                                                         this._setDate(UTCDate(year, month, day,0,0,0,0));
561                                                 }
562                                                 break;
563                                 }
564                         }
565                 },
566
567                 _setDate: function(date, which){
568                         if (!which || which == 'date')
569                                 this.date = date;
570                         if (!which || which  == 'view')
571                                 this.viewDate = date;
572                         this.fill();
573                         this.setValue();
574                         this.element.trigger({
575                                 type: 'changeDate',
576                                 date: this.date
577                         });
578                         var element;
579                         if (this.isInput) {
580                                 element = this.element;
581                         } else if (this.component){
582                                 element = this.element.find('input');
583                         }
584                         if (element) {
585                                 element.change();
586                                 if (this.autoclose && (!which || which == 'date')) {
587                                         this.hide();
588                                 }
589                         }
590                 },
591
592                 moveMonth: function(date, dir){
593                         if (!dir) return date;
594                         var new_date = new Date(date.valueOf()),
595                                 day = new_date.getUTCDate(),
596                                 month = new_date.getUTCMonth(),
597                                 mag = Math.abs(dir),
598                                 new_month, test;
599                         dir = dir > 0 ? 1 : -1;
600                         if (mag == 1){
601                                 test = dir == -1
602                                         // If going back one month, make sure month is not current month
603                                         // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
604                                         ? function(){ return new_date.getUTCMonth() == month; }
605                                         // If going forward one month, make sure month is as expected
606                                         // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
607                                         : function(){ return new_date.getUTCMonth() != new_month; };
608                                 new_month = month + dir;
609                                 new_date.setUTCMonth(new_month);
610                                 // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
611                                 if (new_month < 0 || new_month > 11)
612                                         new_month = (new_month + 12) % 12;
613                         } else {
614                                 // For magnitudes >1, move one month at a time...
615                                 for (var i=0; i<mag; i++)
616                                         // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
617                                         new_date = this.moveMonth(new_date, dir);
618                                 // ...then reset the day, keeping it in the new month
619                                 new_month = new_date.getUTCMonth();
620                                 new_date.setUTCDate(day);
621                                 test = function(){ return new_month != new_date.getUTCMonth(); };
622                         }
623                         // Common date-resetting loop -- if date is beyond end of month, make it
624                         // end of month
625                         while (test()){
626                                 new_date.setUTCDate(--day);
627                                 new_date.setUTCMonth(new_month);
628                         }
629                         return new_date;
630                 },
631
632                 moveYear: function(date, dir){
633                         return this.moveMonth(date, dir*12);
634                 },
635
636                 dateWithinRange: function(date){
637                         return date >= this.startDate && date <= this.endDate;
638                 },
639
640                 keydown: function(e){
641                         if (this.picker.is(':not(:visible)')){
642                                 if (e.keyCode == 27) // allow escape to hide and re-show picker
643                                         this.show();
644                                 return;
645                         }
646                         var dateChanged = false,
647                                 dir, day, month,
648                                 newDate, newViewDate;
649                         switch(e.keyCode){
650                                 case 27: // escape
651                                         this.hide();
652                                         e.preventDefault();
653                                         break;
654                                 case 37: // left
655                                 case 39: // right
656                                         if (!this.keyboardNavigation) break;
657                                         dir = e.keyCode == 37 ? -1 : 1;
658                                         if (e.ctrlKey){
659                                                 newDate = this.moveYear(this.date, dir);
660                                                 newViewDate = this.moveYear(this.viewDate, dir);
661                                         } else if (e.shiftKey){
662                                                 newDate = this.moveMonth(this.date, dir);
663                                                 newViewDate = this.moveMonth(this.viewDate, dir);
664                                         } else {
665                                                 newDate = new Date(this.date);
666                                                 newDate.setUTCDate(this.date.getUTCDate() + dir);
667                                                 newViewDate = new Date(this.viewDate);
668                                                 newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir);
669                                         }
670                                         if (this.dateWithinRange(newDate)){
671                                                 this.date = newDate;
672                                                 this.viewDate = newViewDate;
673                                                 this.setValue();
674                                                 this.update();
675                                                 e.preventDefault();
676                                                 dateChanged = true;
677                                         }
678                                         break;
679                                 case 38: // up
680                                 case 40: // down
681                                         if (!this.keyboardNavigation) break;
682                                         dir = e.keyCode == 38 ? -1 : 1;
683                                         if (e.ctrlKey){
684                                                 newDate = this.moveYear(this.date, dir);
685                                                 newViewDate = this.moveYear(this.viewDate, dir);
686                                         } else if (e.shiftKey){
687                                                 newDate = this.moveMonth(this.date, dir);
688                                                 newViewDate = this.moveMonth(this.viewDate, dir);
689                                         } else {
690                                                 newDate = new Date(this.date);
691                                                 newDate.setUTCDate(this.date.getUTCDate() + dir * 7);
692                                                 newViewDate = new Date(this.viewDate);
693                                                 newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7);
694                                         }
695                                         if (this.dateWithinRange(newDate)){
696                                                 this.date = newDate;
697                                                 this.viewDate = newViewDate;
698                                                 this.setValue();
699                                                 this.update();
700                                                 e.preventDefault();
701                                                 dateChanged = true;
702                                         }
703                                         break;
704                                 case 13: // enter
705                                         this.hide();
706                                         e.preventDefault();
707                                         break;
708                                 case 9: // tab
709                                         this.hide();
710                                         break;
711                         }
712                         if (dateChanged){
713                                 this.element.trigger({
714                                         type: 'changeDate',
715                                         date: this.date
716                                 });
717                                 var element;
718                                 if (this.isInput) {
719                                         element = this.element;
720                                 } else if (this.component){
721                                         element = this.element.find('input');
722                                 }
723                                 if (element) {
724                                         element.change();
725                                 }
726                         }
727                 },
728
729                 showMode: function(dir) {
730                         if (dir) {
731                                 this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir));
732                         }
733                         /*
734                                 vitalets: fixing bug of very special conditions:
735                                 jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover.
736                                 Method show() does not set display css correctly and datepicker is not shown.
737                                 Changed to .css('display', 'block') solve the problem.
738                                 See https://github.com/vitalets/x-editable/issues/37
739
740                                 In jquery 1.7.2+ everything works fine.
741                         */
742                         //this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
743                         this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block');
744                         this.updateNavArrows();
745                 }
746         };
747
748         $.fn.datepicker = function ( option ) {
749                 var args = Array.apply(null, arguments);
750                 args.shift();
751                 return this.each(function () {
752                         var $this = $(this),
753                                 data = $this.data('datepicker'),
754                                 options = typeof option == 'object' && option;
755                         if (!data) {
756                                 $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
757                         }
758                         if (typeof option == 'string' && typeof data[option] == 'function') {
759                                 data[option].apply(data, args);
760                         }
761                 });
762         };
763
764         $.fn.datepicker.defaults = {
765         };
766         $.fn.datepicker.Constructor = Datepicker;
767         var dates = $.fn.datepicker.dates = {
768                 en: {
769                         days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
770                         daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
771                         daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
772                         months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
773                         monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
774                         today: "Today"
775                 }
776         };
777
778         var DPGlobal = {
779                 modes: [
780                         {
781                                 clsName: 'days',
782                                 navFnc: 'Month',
783                                 navStep: 1
784                         },
785                         {
786                                 clsName: 'months',
787                                 navFnc: 'FullYear',
788                                 navStep: 1
789                         },
790                         {
791                                 clsName: 'years',
792                                 navFnc: 'FullYear',
793                                 navStep: 10
794                 }],
795                 isLeapYear: function (year) {
796                         return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
797                 },
798                 getDaysInMonth: function (year, month) {
799                         return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
800                 },
801                 validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,
802                 nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,
803                 parseFormat: function(format){
804                         // IE treats \0 as a string end in inputs (truncating the value),
805                         // so it's a bad format delimiter, anyway
806                         var separators = format.replace(this.validParts, '\0').split('\0'),
807                                 parts = format.match(this.validParts);
808                         if (!separators || !separators.length || !parts || parts.length === 0){
809                                 throw new Error("Invalid date format.");
810                         }
811                         return {separators: separators, parts: parts};
812                 },
813                 parseDate: function(date, format, language) {
814                         if (date instanceof Date) return date;
815                         if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) {
816                                 var part_re = /([\-+]\d+)([dmwy])/,
817                                         parts = date.match(/([\-+]\d+)([dmwy])/g),
818                                         part, dir;
819                                 date = new Date();
820                                 for (var i=0; i<parts.length; i++) {
821                                         part = part_re.exec(parts[i]);
822                                         dir = parseInt(part[1]);
823                                         switch(part[2]){
824                                                 case 'd':
825                                                         date.setUTCDate(date.getUTCDate() + dir);
826                                                         break;
827                                                 case 'm':
828                                                         date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);
829                                                         break;
830                                                 case 'w':
831                                                         date.setUTCDate(date.getUTCDate() + dir * 7);
832                                                         break;
833                                                 case 'y':
834                                                         date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);
835                                                         break;
836                                         }
837                                 }
838                                 return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
839                         }
840                         var parts = date && date.match(this.nonpunctuation) || [],
841                                 date = new Date(),
842                                 parsed = {},
843                                 setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
844                                 setters_map = {
845                                         yyyy: function(d,v){ return d.setUTCFullYear(v); },
846                                         yy: function(d,v){ return d.setUTCFullYear(2000+v); },
847                                         m: function(d,v){
848                                                 v -= 1;
849                                                 while (v<0) v += 12;
850                                                 v %= 12;
851                                                 d.setUTCMonth(v);
852                                                 while (d.getUTCMonth() != v)
853                                                         d.setUTCDate(d.getUTCDate()-1);
854                                                 return d;
855                                         },
856                                         d: function(d,v){ return d.setUTCDate(v); }
857                                 },
858                                 val, filtered, part;
859                         setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
860                         setters_map['dd'] = setters_map['d'];
861                         date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
862                         var fparts = format.parts.slice();
863                         // Remove noop parts
864                         if (parts.length != fparts.length) {
865                                 fparts = $(fparts).filter(function(i,p){
866                                         return $.inArray(p, setters_order) !== -1;
867                                 }).toArray();
868                         }
869                         // Process remainder
870                         if (parts.length == fparts.length) {
871                                 for (var i=0, cnt = fparts.length; i < cnt; i++) {
872                                         val = parseInt(parts[i], 10);
873                                         part = fparts[i];
874                                         if (isNaN(val)) {
875                                                 switch(part) {
876                                                         case 'MM':
877                                                                 filtered = $(dates[language].months).filter(function(){
878                                                                         var m = this.slice(0, parts[i].length),
879                                                                                 p = parts[i].slice(0, m.length);
880                                                                         return m == p;
881                                                                 });
882                                                                 val = $.inArray(filtered[0], dates[language].months) + 1;
883                                                                 break;
884                                                         case 'M':
885                                                                 filtered = $(dates[language].monthsShort).filter(function(){
886                                                                         var m = this.slice(0, parts[i].length),
887                                                                                 p = parts[i].slice(0, m.length);
888                                                                         return m == p;
889                                                                 });
890                                                                 val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
891                                                                 break;
892                                                 }
893                                         }
894                                         parsed[part] = val;
895                                 }
896                                 for (var i=0, s; i<setters_order.length; i++){
897                                         s = setters_order[i];
898                                         if (s in parsed && !isNaN(parsed[s]))
899                                                 setters_map[s](date, parsed[s]);
900                                 }
901                         }
902                         return date;
903                 },
904                 formatDate: function(date, format, language){
905                         var val = {
906                                 d: date.getUTCDate(),
907                                 D: dates[language].daysShort[date.getUTCDay()],
908                                 DD: dates[language].days[date.getUTCDay()],
909                                 m: date.getUTCMonth() + 1,
910                                 M: dates[language].monthsShort[date.getUTCMonth()],
911                                 MM: dates[language].months[date.getUTCMonth()],
912                                 yy: date.getUTCFullYear().toString().substring(2),
913                                 yyyy: date.getUTCFullYear()
914                         };
915                         val.dd = (val.d < 10 ? '0' : '') + val.d;
916                         val.mm = (val.m < 10 ? '0' : '') + val.m;
917                         var date = [],
918                                 seps = $.extend([], format.separators);
919                         for (var i=0, cnt = format.parts.length; i < cnt; i++) {
920                                 if (seps.length)
921                                         date.push(seps.shift());
922                                 date.push(val[format.parts[i]]);
923                         }
924                         return date.join('');
925                 },
926                 headTemplate: '<thead>'+
927                                                         '<tr>'+
928                                                                 '<th class="prev"><i class="icon-arrow-left"/></th>'+
929                                                                 '<th colspan="5" class="switch"></th>'+
930                                                                 '<th class="next"><i class="icon-arrow-right"/></th>'+
931                                                         '</tr>'+
932                                                 '</thead>',
933                 contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
934                 footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr></tfoot>'
935         };
936         DPGlobal.template = '<div class="datepicker">'+
937                                                         '<div class="datepicker-days">'+
938                                                                 '<table class=" table-condensed">'+
939                                                                         DPGlobal.headTemplate+
940                                                                         '<tbody></tbody>'+
941                                                                         DPGlobal.footTemplate+
942                                                                 '</table>'+
943                                                         '</div>'+
944                                                         '<div class="datepicker-months">'+
945                                                                 '<table class="table-condensed">'+
946                                                                         DPGlobal.headTemplate+
947                                                                         DPGlobal.contTemplate+
948                                                                         DPGlobal.footTemplate+
949                                                                 '</table>'+
950                                                         '</div>'+
951                                                         '<div class="datepicker-years">'+
952                                                                 '<table class="table-condensed">'+
953                                                                         DPGlobal.headTemplate+
954                                                                         DPGlobal.contTemplate+
955                                                                         DPGlobal.footTemplate+
956                                                                 '</table>'+
957                                                         '</div>'+
958                                                 '</div>';
959
960         $.fn.datepicker.DPGlobal = DPGlobal;
961
962 }( window.jQuery );