tutorial in editor (mechanics only)
[redakcja.git] / redakcja / static / datepicker / js / bootstrap-datepicker.js
1 /* =========================================================
2  * bootstrap-datepicker.js 
3  * http://www.eyecon.ro/bootstrap-datepicker
4  * =========================================================
5  * Copyright 2012 Stefan Petre
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ========================================================= */
19  
20 !function( $ ) {
21         
22         // Picker object
23         
24         var Datepicker = function(element, options){
25                 this.element = $(element);
26                 this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
27                 this.picker = $(DPGlobal.template)
28                                                         .appendTo('body')
29                                                         .on({
30                                                                 click: $.proxy(this.click, this)//,
31                                                                 //mousedown: $.proxy(this.mousedown, this)
32                                                         });
33                 this.isInput = this.element.is('input');
34                 this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
35                 
36                 if (this.isInput) {
37                         this.element.on({
38                                 focus: $.proxy(this.show, this),
39                                 //blur: $.proxy(this.hide, this),
40                                 keyup: $.proxy(this.update, this)
41                         });
42                 } else {
43                         if (this.component){
44                                 this.component.on('click', $.proxy(this.show, this));
45                         } else {
46                                 this.element.on('click', $.proxy(this.show, this));
47                         }
48                 }
49         
50                 this.minViewMode = options.minViewMode||this.element.data('date-minviewmode')||0;
51                 if (typeof this.minViewMode === 'string') {
52                         switch (this.minViewMode) {
53                                 case 'months':
54                                         this.minViewMode = 1;
55                                         break;
56                                 case 'years':
57                                         this.minViewMode = 2;
58                                         break;
59                                 default:
60                                         this.minViewMode = 0;
61                                         break;
62                         }
63                 }
64                 this.viewMode = options.viewMode||this.element.data('date-viewmode')||0;
65                 if (typeof this.viewMode === 'string') {
66                         switch (this.viewMode) {
67                                 case 'months':
68                                         this.viewMode = 1;
69                                         break;
70                                 case 'years':
71                                         this.viewMode = 2;
72                                         break;
73                                 default:
74                                         this.viewMode = 0;
75                                         break;
76                         }
77                 }
78                 this.startViewMode = this.viewMode;
79                 this.weekStart = options.weekStart||this.element.data('date-weekstart')||0;
80                 this.weekEnd = this.weekStart === 0 ? 6 : this.weekStart - 1;
81                 this.onRender = options.onRender;
82                 this.fillDow();
83                 this.fillMonths();
84                 this.update();
85                 this.showMode();
86         };
87         
88         Datepicker.prototype = {
89                 constructor: Datepicker,
90                 
91                 show: function(e) {
92                         this.picker.show();
93                         this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
94                         this.place();
95                         $(window).on('resize', $.proxy(this.place, this));
96                         if (e ) {
97                                 e.stopPropagation();
98                                 e.preventDefault();
99                         }
100                         if (!this.isInput) {
101                         }
102                         var that = this;
103                         $(document).on('mousedown', function(ev){
104                                 if ($(ev.target).closest('.datepicker').length == 0) {
105                                         that.hide();
106                                 }
107                         });
108                         this.element.trigger({
109                                 type: 'show',
110                                 date: this.date
111                         });
112                 },
113                 
114                 hide: function(){
115                         this.picker.hide();
116                         $(window).off('resize', this.place);
117                         this.viewMode = this.startViewMode;
118                         this.showMode();
119                         if (!this.isInput) {
120                                 $(document).off('mousedown', this.hide);
121                         }
122                         //this.set();
123                         this.element.trigger({
124                                 type: 'hide',
125                                 date: this.date
126                         });
127                 },
128                 
129                 set: function() {
130                         var formated = DPGlobal.formatDate(this.date, this.format);
131                         if (!this.isInput) {
132                                 if (this.component){
133                                         this.element.find('input').prop('value', formated);
134                                 }
135                                 this.element.data('date', formated);
136                         } else {
137                                 this.element.prop('value', formated);
138                         }
139                 },
140                 
141                 setValue: function(newDate) {
142                         if (typeof newDate === 'string') {
143                                 this.date = DPGlobal.parseDate(newDate, this.format);
144                         } else {
145                                 this.date = new Date(newDate);
146                         }
147                         this.set();
148                         this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
149                         this.fill();
150                 },
151                 
152                 place: function(){
153                         var offset = this.component ? this.component.offset() : this.element.offset();
154                         this.picker.css({
155                                 top: offset.top + this.height,
156                                 left: offset.left
157                         });
158                 },
159                 
160                 update: function(newDate){
161                         this.date = DPGlobal.parseDate(
162                                 typeof newDate === 'string' ? newDate : (this.isInput ? this.element.prop('value') : this.element.data('date')),
163                                 this.format
164                         );
165                         this.viewDate = new Date(this.date.getFullYear(), this.date.getMonth(), 1, 0, 0, 0, 0);
166                         this.fill();
167                 },
168                 
169                 fillDow: function(){
170                         var dowCnt = this.weekStart;
171                         var html = '<tr>';
172                         while (dowCnt < this.weekStart + 7) {
173                                 html += '<th class="dow">'+DPGlobal.dates.daysMin[(dowCnt++)%7]+'</th>';
174                         }
175                         html += '</tr>';
176                         this.picker.find('.datepicker-days thead').append(html);
177                 },
178                 
179                 fillMonths: function(){
180                         var html = '';
181                         var i = 0
182                         while (i < 12) {
183                                 html += '<span class="month">'+DPGlobal.dates.monthsShort[i++]+'</span>';
184                         }
185                         this.picker.find('.datepicker-months td').append(html);
186                 },
187                 
188                 fill: function() {
189                         var d = new Date(this.viewDate),
190                                 year = d.getFullYear(),
191                                 month = d.getMonth(),
192                                 currentDate = this.date.valueOf();
193                         this.picker.find('.datepicker-days th:eq(1)')
194                                                 .text(DPGlobal.dates.months[month]+' '+year);
195                         var prevMonth = new Date(year, month-1, 28,0,0,0,0),
196                                 day = DPGlobal.getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth());
197                         prevMonth.setDate(day);
198                         prevMonth.setDate(day - (prevMonth.getDay() - this.weekStart + 7)%7);
199                         var nextMonth = new Date(prevMonth);
200                         nextMonth.setDate(nextMonth.getDate() + 42);
201                         nextMonth = nextMonth.valueOf();
202                         var html = [];
203                         var clsName,
204                                 prevY,
205                                 prevM;
206                         while(prevMonth.valueOf() < nextMonth) {
207                                 if (prevMonth.getDay() === this.weekStart) {
208                                         html.push('<tr>');
209                                 }
210                                 clsName = this.onRender(prevMonth);
211                                 prevY = prevMonth.getFullYear();
212                                 prevM = prevMonth.getMonth();
213                                 if ((prevM < month &&  prevY === year) ||  prevY < year) {
214                                         clsName += ' old';
215                                 } else if ((prevM > month && prevY === year) || prevY > year) {
216                                         clsName += ' new';
217                                 }
218                                 if (prevMonth.valueOf() === currentDate) {
219                                         clsName += ' active';
220                                 }
221                                 html.push('<td class="day '+clsName+'">'+prevMonth.getDate() + '</td>');
222                                 if (prevMonth.getDay() === this.weekEnd) {
223                                         html.push('</tr>');
224                                 }
225                                 prevMonth.setDate(prevMonth.getDate()+1);
226                         }
227                         this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
228                         var currentYear = this.date.getFullYear();
229                         
230                         var months = this.picker.find('.datepicker-months')
231                                                 .find('th:eq(1)')
232                                                         .text(year)
233                                                         .end()
234                                                 .find('span').removeClass('active');
235                         if (currentYear === year) {
236                                 months.eq(this.date.getMonth()).addClass('active');
237                         }
238                         
239                         html = '';
240                         year = parseInt(year/10, 10) * 10;
241                         var yearCont = this.picker.find('.datepicker-years')
242                                                                 .find('th:eq(1)')
243                                                                         .text(year + '-' + (year + 9))
244                                                                         .end()
245                                                                 .find('td');
246                         year -= 1;
247                         for (var i = -1; i < 11; i++) {
248                                 html += '<span class="year'+(i === -1 || i === 10 ? ' old' : '')+(currentYear === year ? ' active' : '')+'">'+year+'</span>';
249                                 year += 1;
250                         }
251                         yearCont.html(html);
252                 },
253                 
254                 click: function(e) {
255                         e.stopPropagation();
256                         e.preventDefault();
257                         var target = $(e.target).closest('span, td, th');
258                         if (target.length === 1) {
259                                 switch(target[0].nodeName.toLowerCase()) {
260                                         case 'th':
261                                                 switch(target[0].className) {
262                                                         case 'switch':
263                                                                 this.showMode(1);
264                                                                 break;
265                                                         case 'prev':
266                                                         case 'next':
267                                                                 this.viewDate['set'+DPGlobal.modes[this.viewMode].navFnc].call(
268                                                                         this.viewDate,
269                                                                         this.viewDate['get'+DPGlobal.modes[this.viewMode].navFnc].call(this.viewDate) + 
270                                                                         DPGlobal.modes[this.viewMode].navStep * (target[0].className === 'prev' ? -1 : 1)
271                                                                 );
272                                                                 this.fill();
273                                                                 this.set();
274                                                                 break;
275                                                 }
276                                                 break;
277                                         case 'span':
278                                                 if (target.is('.month')) {
279                                                         var month = target.parent().find('span').index(target);
280                                                         this.viewDate.setMonth(month);
281                                                 } else {
282                                                         var year = parseInt(target.text(), 10)||0;
283                                                         this.viewDate.setFullYear(year);
284                                                 }
285                                                 if (this.viewMode !== 0) {
286                                                         this.date = new Date(this.viewDate);
287                                                         this.element.trigger({
288                                                                 type: 'changeDate',
289                                                                 date: this.date,
290                                                                 viewMode: DPGlobal.modes[this.viewMode].clsName
291                                                         });
292                                                 }
293                                                 this.showMode(-1);
294                                                 this.fill();
295                                                 this.set();
296                                                 break;
297                                         case 'td':
298                                                 if (target.is('.day') && !target.is('.disabled')){
299                                                         var day = parseInt(target.text(), 10)||1;
300                                                         var month = this.viewDate.getMonth();
301                                                         if (target.is('.old')) {
302                                                                 month -= 1;
303                                                         } else if (target.is('.new')) {
304                                                                 month += 1;
305                                                         }
306                                                         var year = this.viewDate.getFullYear();
307                                                         this.date = new Date(year, month, day,0,0,0,0);
308                                                         this.viewDate = new Date(year, month, Math.min(28, day),0,0,0,0);
309                                                         this.fill();
310                                                         this.set();
311                                                         this.element.trigger({
312                                                                 type: 'changeDate',
313                                                                 date: this.date,
314                                                                 viewMode: DPGlobal.modes[this.viewMode].clsName
315                                                         });
316                                                 }
317                                                 break;
318                                 }
319                         }
320                 },
321                 
322                 mousedown: function(e){
323                         e.stopPropagation();
324                         e.preventDefault();
325                 },
326                 
327                 showMode: function(dir) {
328                         if (dir) {
329                                 this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir));
330                         }
331                         this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
332                 }
333         };
334         
335         $.fn.datepicker = function ( option, val ) {
336                 return this.each(function () {
337                         var $this = $(this),
338                                 data = $this.data('datepicker'),
339                                 options = typeof option === 'object' && option;
340                         if (!data) {
341                                 $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
342                         }
343                         if (typeof option === 'string') data[option](val);
344                 });
345         };
346
347         $.fn.datepicker.defaults = {
348                 onRender: function(date) {
349                         return '';
350                 }
351         };
352         $.fn.datepicker.Constructor = Datepicker;
353         
354         var DPGlobal = {
355                 modes: [
356                         {
357                                 clsName: 'days',
358                                 navFnc: 'Month',
359                                 navStep: 1
360                         },
361                         {
362                                 clsName: 'months',
363                                 navFnc: 'FullYear',
364                                 navStep: 1
365                         },
366                         {
367                                 clsName: 'years',
368                                 navFnc: 'FullYear',
369                                 navStep: 10
370                 }],
371                 dates:{
372                         days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
373                         daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
374                         daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
375                         months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
376                         monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
377                 },
378                 isLeapYear: function (year) {
379                         return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
380                 },
381                 getDaysInMonth: function (year, month) {
382                         return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
383                 },
384                 parseFormat: function(format){
385                         var separator = format.match(/[.\/\-\s].*?/),
386                                 parts = format.split(/\W+/);
387                         if (!separator || !parts || parts.length === 0){
388                                 throw new Error("Invalid date format.");
389                         }
390                         return {separator: separator, parts: parts};
391                 },
392                 parseDate: function(date, format) {
393                         var parts = date.split(format.separator),
394                                 date = new Date(),
395                                 val;
396                         date.setHours(0);
397                         date.setMinutes(0);
398                         date.setSeconds(0);
399                         date.setMilliseconds(0);
400                         if (parts.length === format.parts.length) {
401                                 var year = date.getFullYear(), day = date.getDate(), month = date.getMonth();
402                                 for (var i=0, cnt = format.parts.length; i < cnt; i++) {
403                                         val = parseInt(parts[i], 10)||1;
404                                         switch(format.parts[i]) {
405                                                 case 'dd':
406                                                 case 'd':
407                                                         day = val;
408                                                         date.setDate(val);
409                                                         break;
410                                                 case 'mm':
411                                                 case 'm':
412                                                         month = val - 1;
413                                                         date.setMonth(val - 1);
414                                                         break;
415                                                 case 'yy':
416                                                         year = 2000 + val;
417                                                         date.setFullYear(2000 + val);
418                                                         break;
419                                                 case 'yyyy':
420                                                         year = val;
421                                                         date.setFullYear(val);
422                                                         break;
423                                         }
424                                 }
425                                 date = new Date(year, month, day, 0 ,0 ,0);
426                         }
427                         return date;
428                 },
429                 formatDate: function(date, format){
430                         var val = {
431                                 d: date.getDate(),
432                                 m: date.getMonth() + 1,
433                                 yy: date.getFullYear().toString().substring(2),
434                                 yyyy: date.getFullYear()
435                         };
436                         val.dd = (val.d < 10 ? '0' : '') + val.d;
437                         val.mm = (val.m < 10 ? '0' : '') + val.m;
438                         var date = [];
439                         for (var i=0, cnt = format.parts.length; i < cnt; i++) {
440                                 date.push(val[format.parts[i]]);
441                         }
442                         return date.join(format.separator);
443                 },
444                 headTemplate: '<thead>'+
445                                                         '<tr>'+
446                                                                 '<th class="prev">&lsaquo;</th>'+
447                                                                 '<th colspan="5" class="switch"></th>'+
448                                                                 '<th class="next">&rsaquo;</th>'+
449                                                         '</tr>'+
450                                                 '</thead>',
451                 contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>'
452         };
453         DPGlobal.template = '<div class="datepicker dropdown-menu">'+
454                                                         '<div class="datepicker-days">'+
455                                                                 '<table class=" table-condensed">'+
456                                                                         DPGlobal.headTemplate+
457                                                                         '<tbody></tbody>'+
458                                                                 '</table>'+
459                                                         '</div>'+
460                                                         '<div class="datepicker-months">'+
461                                                                 '<table class="table-condensed">'+
462                                                                         DPGlobal.headTemplate+
463                                                                         DPGlobal.contTemplate+
464                                                                 '</table>'+
465                                                         '</div>'+
466                                                         '<div class="datepicker-years">'+
467                                                                 '<table class="table-condensed">'+
468                                                                         DPGlobal.headTemplate+
469                                                                         DPGlobal.contTemplate+
470                                                                 '</table>'+
471                                                         '</div>'+
472                                                 '</div>';
473
474 }( window.jQuery );