init
[garradin.git] / www / admin / static / datepickr.js
1 /*
2 datepickr - pick your date not your nose
3 Copyright (c) 2010 josh.salverda - 2012 bohwaz Apache License 2.0
4 https://code.google.com/p/datepickr/
5 http://dev.kd2.org/garradin/
6 */
7
8 function datepickr(targetElement, userConfig) {
9
10 var config = {
11 fullCurrentMonth: true,
12 dateFormat: 'F jS, Y',
13 firstDayOfWeek: 1,
14 weekdays: ['Sun', 'Mon', 'Tues', 'Wednes', 'Thurs', 'Fri', 'Satur'],
15 months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
16 suffix: { 1: 'st', 2: 'nd', 3: 'rd', 21: 'st', 22: 'nd', 23: 'rd', 31: 'st' },
17 defaultSuffix: 'th'
18 },
19 currentDate = new Date(),
20 currentPosition = new Array(0,0),
21 currentMaxRows = 4,
22 // shortcuts to get date info
23 get = {
24 current: {
25 year: function() {
26 return currentDate.getFullYear();
27 },
28 month: {
29 integer: function() {
30 return currentDate.getMonth();
31 },
32 string: function(full) {
33 var date = currentDate.getMonth();
34 return monthToStr(date, full);
35 }
36 },
37 day: function() {
38 return currentDate.getDate();
39 }
40 },
41 month: {
42 integer: function() {
43 return currentMonthView;
44 },
45 string: function(full) {
46 var date = currentMonthView;
47 return monthToStr(date, full);
48 },
49 numDays: function() {
50 // checks to see if february is a leap year otherwise return the respective # of days
51 return (get.month.integer() == 1 && !(currentYearView & 3) && (currentYearView % 1e2 || !(currentYearView % 4e2))) ? 29 : daysInMonth[get.month.integer()];
52 }
53 }
54 },
55 // variables used throughout the class
56 daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
57 element, container, body, month, prevMonth, nextMonth,
58 currentYearView = get.current.year(),
59 currentMonthView = get.current.month.integer(),
60 i, x, buildCache = [];
61
62 function build(nodeName, attributes, content) {
63 var element;
64
65 if(!(nodeName in buildCache)) {
66 buildCache[nodeName] = document.createElement(nodeName);
67 }
68
69 element = buildCache[nodeName].cloneNode(false);
70
71 if(attributes != null) {
72 for(var attribute in attributes) {
73 element[attribute] = attributes[attribute];
74 }
75 }
76
77 if(content != null) {
78 if(typeof(content) == 'object') {
79 element.appendChild(content);
80 } else {
81 element.innerHTML = content;
82 }
83 }
84
85 return element;
86 }
87
88 function monthToStr(date, full) {
89 return ((full == true) ? config.months[date] : ((config.months[date].length > 3) ? config.months[date].substring(0, 3) : config.months[date]));
90 }
91
92 function formatDate(milliseconds) {
93 var formattedDate = '',
94 dateObj = new Date(milliseconds),
95 format = {
96 d: function() {
97 var day = format.j();
98 return (day < 10) ? '0' + day : day;
99 },
100 D: function() {
101 return config.weekdays[format.w()].substring(0, 3);
102 },
103 j: function() {
104 return dateObj.getDate();
105 },
106 l: function() {
107 return config.weekdays[format.w()] + 'day';
108 },
109 S: function() {
110 return config.suffix[format.j()] || config.defaultSuffix;
111 },
112 w: function() {
113 return dateObj.getDay();
114 },
115 F: function() {
116 return monthToStr(format.n(), true);
117 },
118 m: function() {
119 var month = format.n() + 1;
120 return (month < 10) ? '0' + month : month;
121 },
122 M: function() {
123 return monthToStr(format.n(), false);
124 },
125 n: function() {
126 return dateObj.getMonth();
127 },
128 Y: function() {
129 return dateObj.getFullYear();
130 },
131 y: function() {
132 return format.Y().substring(2, 4);
133 }
134 },
135 formatPieces = config.dateFormat.split('');
136
137 for(i = 0, x = formatPieces.length; i < x; i++) {
138 formattedDate += format[formatPieces[i]] ? format[formatPieces[i]]() : formatPieces[i];
139 }
140
141 return formattedDate;
142 }
143
144 function handleMonthClick() {
145 // if we go too far into the past
146 if(currentMonthView < 0) {
147 currentYearView--;
148
149 // start our month count at 11 (11 = december)
150 currentMonthView = 11;
151 }
152
153 // if we go too far into the future
154 if(currentMonthView > 11) {
155 currentYearView++;
156
157 // restart our month count (0 = january)
158 currentMonthView = 0;
159 }
160
161 month.innerHTML = get.month.string(config.fullCurrentMonth) + ' ' + currentYearView;
162
163 // rebuild the calendar
164 while(body.hasChildNodes()){
165 body.removeChild(body.lastChild);
166 }
167 body.appendChild(buildCalendar());
168 bindDayLinks();
169
170 return false;
171 }
172
173 function bindMonthLinks() {
174 prevMonth.onclick = function() {
175 currentMonthView--;
176 return handleMonthClick();
177 }
178
179 nextMonth.onclick = function() {
180 currentMonthView++;
181 return handleMonthClick();
182 }
183 }
184
185 // our link binding function
186 function bindDayLinks() {
187 var days = body.getElementsByTagName('a');
188
189 for(i = 0, x = days.length; i < x; i++) {
190 days[i].onclick = function() {
191 currentDate = new Date(currentYearView, currentMonthView, this.innerHTML);
192 element.value = formatDate(currentDate.getTime());
193 element.onchange(element);
194 close();
195 return false;
196 }
197 }
198 }
199
200 function buildWeekdays() {
201 var html = document.createDocumentFragment();
202 // write out the names of each week day
203 for(i = 0, x = config.weekdays.length; i < x; i++) {
204 html.appendChild(build('th', {}, config.weekdays[i].substring(0, 2)));
205 }
206 return html;
207 }
208
209 function buildCalendar() {
210 // get the first day of the month we are currently viewing
211 var firstOfMonth = new Date(currentYearView, currentMonthView, config.firstDayOfWeek).getDay(),
212 // get the total number of days in the month we are currently viewing
213 numDays = get.month.numDays(),
214 // declare our day counter
215 dayCount = 0,
216 weekCount = 0,
217 html = document.createDocumentFragment(),
218 row = build('tr');
219
220 // print out previous month's "days"
221 for(i = 1; i <= firstOfMonth; i++) {
222 row.appendChild(build('td', {}, ''));
223 dayCount++;
224 }
225
226 for(i = 1; i <= numDays; i++) {
227 // if we have reached the end of a week, wrap to the next line
228 if(dayCount == 7) {
229 html.appendChild(row);
230 row = build('tr');
231 dayCount = 0;
232 weekCount++;
233 }
234
235 // output the text that goes inside each td
236 // if the day is the current day, add a class of "today"
237 var today = (i == get.current.day() && currentMonthView == get.current.month.integer() && currentYearView == get.current.year());
238 if (today)
239 {
240 currentPosition = [weekCount+1, dayCount];
241 }
242 row.appendChild(build('td', { className: today ? 'today' : '' }, build('a', { href: 'javascript:void(0)' }, i)));
243 dayCount++;
244 }
245
246 // if we haven't finished at the end of the week, start writing out the "days" for the next month
247 for(i = 1; i <= (7 - dayCount); i++) {
248 row.appendChild(build('td', {}, ''));
249 }
250
251 html.appendChild(row);
252
253 currentMaxRows = weekCount+1;
254
255 return html;
256 }
257
258 function open() {
259 document.onmousedown = function(e) {
260 e = e || window.event;
261 var target = e.target || e.srcElement;
262
263 var parentNode = target.parentNode;
264 if(target != element && parentNode != container) {
265 while(parentNode != container) {
266 parentNode = parentNode.parentNode;
267 if(parentNode == null) {
268 close();
269 break;
270 }
271 }
272 }
273
274 if (target == element)
275 {
276 close();
277 }
278
279 e.preventDefault();
280 }
281
282 document.onkeyup = function(e) {
283 var k = e.keyCode || e.which;
284
285 if (k == 27)
286 {
287 close();
288 e.preventDefault();
289 return false;
290 }
291 };
292
293 document.onkeypress = function(e) {
294 var k = e.keyCode || e.which;
295
296 if (k == 33) // PgUp
297 {
298 e.preventDefault();
299 currentMonthView--;
300 return handleMonthClick();
301 }
302 else if (k == 34) // PgDn
303 {
304 e.preventDefault();
305 currentMonthView++;
306 return handleMonthClick();
307 }
308 else if (k >= 37 && k <= 40) // Arrows
309 {
310 e.preventDefault();
311 var pos = currentPosition.slice();
312 if (k == 37) { // left
313 if (pos[1] == 0) return;
314 pos[1]--;
315 }
316 else if (k == 38) { // up
317 if (pos[0] <= 1) return;
318 pos[0]--;
319 }
320 else if (k == 39) { // right
321 if (pos[1] == 6) return;
322 pos[1]++;
323 }
324 else { // down
325 if (pos[0] == currentMaxRows) return;
326 pos[0]++;
327 }
328
329 var table = container.getElementsByTagName('table')[0];
330 var row = table.getElementsByTagName('td')[pos[0]*7+pos[1]-7];
331
332 if (row.innerHTML == "") return;
333
334 table.getElementsByTagName('td')[currentPosition[0]*7+currentPosition[1]-7].className = '';
335 row.className = 'today';
336
337 currentPosition = pos;
338 currentDate = new Date(currentYearView, currentMonthView, row.firstChild.innerHTML);
339 }
340 else if (k == 13 || k == 32)
341 {
342 element.value = formatDate(currentDate.getTime());
343 element.onchange(element);
344 close();
345 e.preventDefault();
346 return false;
347 }
348 }
349
350 handleMonthClick();
351 container.style.display = 'block';
352 }
353
354 function close() {
355 document.onmousedown = null;
356 document.onkeypress = null;
357 container.style.display = 'none';
358 }
359
360 function initialise(userConfig) {
361 if(userConfig) {
362 for(var key in userConfig) {
363 if(config.hasOwnProperty(key)) {
364 config[key] = userConfig[key];
365 }
366 }
367 }
368
369 if (element.value)
370 {
371 var d = element.value.split('/').reverse();
372 currentDate = new Date(parseInt(d[0], 10), parseInt(d[1], 10) - 1, parseInt(d[2], 10), 0, 0, 0, 0);
373 currentYearView = get.current.year();
374 currentMonthView = get.current.month.integer();
375 }
376 container = build('div', { className: 'calendar' });
377 container.style.cssText = 'display: none; position: absolute; z-index: 9999;';
378
379 var months = build('div', { className: 'months' });
380 prevMonth = build('span', { className: 'prev-month' }, build('a', { href: '#' }, '&lt;'));
381 nextMonth = build('span', { className: 'next-month' }, build('a', { href: '#' }, '&gt;'));
382 month = build('span', { className: 'current-month' }, get.month.string(config.fullCurrentMonth) + ' ' + currentYearView);
383
384 months.appendChild(prevMonth);
385 months.appendChild(nextMonth);
386 months.appendChild(month);
387
388 var calendar = build('table', {}, build('thead', {}, build('tr', { className: 'weekdays' }, buildWeekdays())));
389 body = build('tbody', {}, buildCalendar());
390
391 calendar.appendChild(body);
392
393 container.appendChild(months);
394 container.appendChild(calendar);
395
396 element.parentNode.style.position = 'relative';
397 element.parentNode.appendChild(container);
398
399 bindMonthLinks();
400
401 element.onfocus = open;
402 element.onblur = close;
403 }
404
405 return (function() {
406 element = typeof(targetElement) == 'string' ? document.getElementById(targetElement) : targetElement;
407 initialise(userConfig);
408 })();
409 }
410
411 // Add-on for HTML5 input type="date" fallback
412
413 (function() {
414 var config_fr = {
415 fullCurrentMonth: true,
416 dateFormat: 'd/m/Y',
417 firstDayOfWeek: 0,
418 weekdays: ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'],
419 months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
420 suffix: { 1: 'er' },
421 defaultSuffix: ''
422 };
423
424 function dateInputFallback()
425 {
426 var inputs = document.getElementsByTagName('input');
427 var length = inputs.length;
428 var enabled = false;
429
430 for (i = 0; i < inputs.length; i++)
431 {
432 if (inputs[i].getAttribute('type') == 'date')
433 {
434 var new_input = inputs[i].cloneNode(true);
435 inputs[i].type = 'hidden';
436 inputs[i].removeAttribute('pattern');
437 inputs[i].removeAttribute('id');
438 inputs[i].removeAttribute('required');
439
440 new_input.removeAttribute('name');
441 new_input.setAttribute('type', 'text');
442 new_input.className += ' date';
443 new_input.size = 10;
444 new_input.maxlength = 10;
445 new_input.value = inputs[i].value.split('-').reverse().join('/');
446 new_input.setAttribute('pattern', '([012][0-9]|3[01])/(0[0-9]|1[0-2])/[12][0-9]{3}');
447
448 new_input.onchange = function ()
449 {
450 if (this.value.match(/\d{2}\/\d{2}\/\d{4}/))
451 this.nextSibling.value = this.value.split('/').reverse().join('-');
452 else
453 this.nextSibling.value = this.value;
454 };
455
456 inputs[i].parentNode.insertBefore(new_input, inputs[i]);
457 new datepickr(new_input, config_fr);
458 }
459 }
460 }
461
462 dateInputFallback();
463 } () );