]> asedeno.scripts.mit.edu Git - bluechips.git/blob - bluechips/public/js/jquery.date_input.js
ripped out toscawidgets, replaced with formencode, put split editing on main expendit...
[bluechips.git] / bluechips / public / js / jquery.date_input.js
1 /*
2 Date Input 1.2.1
3 Requires jQuery version: >= 1.2.6
4
5 Copyright (c) 2007-2008 Jonathan Leighton & Torchbox Ltd
6
7 Permission is hereby granted, free of charge, to any person
8 obtaining a copy of this software and associated documentation
9 files (the "Software"), to deal in the Software without
10 restriction, including without limitation the rights to use,
11 copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the
13 Software is furnished to do so, subject to the following
14 conditions:
15
16 The above copyright notice and this permission notice shall be
17 included in all copies or substantial portions of the Software.
18
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26 OTHER DEALINGS IN THE SOFTWARE.
27 */
28
29 DateInput = (function($) { 
30
31 function DateInput(el, opts) {
32   if (typeof(opts) != "object") opts = {};
33   $.extend(this, DateInput.DEFAULT_OPTS, opts);
34   
35   this.input = $(el);
36   this.bindMethodsToObj("show", "hide", "hideIfClickOutside", "keydownHandler", "selectDate");
37   
38   this.build();
39   this.selectDate();
40   this.hide();
41 };
42 DateInput.DEFAULT_OPTS = {
43   month_names: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
44   short_month_names: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
45   short_day_names: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
46   start_of_week: 1
47 };
48 DateInput.prototype = {
49   build: function() {
50     var monthNav = $('<p class="month_nav">' +
51       '<span class="button prev" title="[Page-Up]">&#171;</span>' +
52       ' <span class="month_name"></span> ' +
53       '<span class="button next" title="[Page-Down]">&#187;</span>' +
54       '</p>');
55     this.monthNameSpan = $(".month_name", monthNav);
56     $(".prev", monthNav).click(this.bindToObj(function() { this.moveMonthBy(-1); }));
57     $(".next", monthNav).click(this.bindToObj(function() { this.moveMonthBy(1); }));
58     
59     var yearNav = $('<p class="year_nav">' +
60       '<span class="button prev" title="[Ctrl+Page-Up]">&#171;</span>' +
61       ' <span class="year_name"></span> ' +
62       '<span class="button next" title="[Ctrl+Page-Down]">&#187;</span>' +
63       '</p>');
64     this.yearNameSpan = $(".year_name", yearNav);
65     $(".prev", yearNav).click(this.bindToObj(function() { this.moveMonthBy(-12); }));
66     $(".next", yearNav).click(this.bindToObj(function() { this.moveMonthBy(12); }));
67     
68     var nav = $('<div class="nav"></div>').append(monthNav, yearNav);
69     
70     var tableShell = "<table><thead><tr>";
71     $(this.adjustDays(this.short_day_names)).each(function() {
72       tableShell += "<th>" + this + "</th>";
73     });
74     tableShell += "</tr></thead><tbody></tbody></table>";
75     
76     this.dateSelector = this.rootLayers = $('<div class="date_selector"></div>').append(nav, tableShell).insertAfter(this.input);
77     
78     if ($.browser.msie && $.browser.version < 7) {
79       
80       this.ieframe = $('<iframe class="date_selector_ieframe" frameborder="0" src="#"></iframe>').insertBefore(this.dateSelector);
81       this.rootLayers = this.rootLayers.add(this.ieframe);
82       
83       $(".button", nav).mouseover(function() { $(this).addClass("hover") });
84       $(".button", nav).mouseout(function() { $(this).removeClass("hover") });
85     };
86     
87     this.tbody = $("tbody", this.dateSelector);
88     
89     this.input.change(this.bindToObj(function() { this.selectDate(); }));
90     this.selectDate();
91   },
92
93   selectMonth: function(date) {
94     var newMonth = new Date(date.getFullYear(), date.getMonth(), 1);
95     
96     if (!this.currentMonth || !(this.currentMonth.getFullYear() == newMonth.getFullYear() &&
97                                 this.currentMonth.getMonth() == newMonth.getMonth())) {
98       
99       this.currentMonth = newMonth;
100       
101       var rangeStart = this.rangeStart(date), rangeEnd = this.rangeEnd(date);
102       var numDays = this.daysBetween(rangeStart, rangeEnd);
103       var dayCells = "";
104       
105       for (var i = 0; i <= numDays; i++) {
106         var currentDay = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate() + i, 12, 00);
107         
108         if (this.isFirstDayOfWeek(currentDay)) dayCells += "<tr>";
109         
110         if (currentDay.getMonth() == date.getMonth()) {
111           dayCells += '<td class="selectable_day" date="' + this.dateToString(currentDay) + '">' + currentDay.getDate() + '</td>';
112         } else {
113           dayCells += '<td class="unselected_month" date="' + this.dateToString(currentDay) + '">' + currentDay.getDate() + '</td>';
114         };
115         
116         if (this.isLastDayOfWeek(currentDay)) dayCells += "</tr>";
117       };
118       this.tbody.empty().append(dayCells);
119       
120       this.monthNameSpan.empty().append(this.monthName(date));
121       this.yearNameSpan.empty().append(this.currentMonth.getFullYear());
122       
123       $(".selectable_day", this.tbody).click(this.bindToObj(function(event) {
124         this.changeInput($(event.target).attr("date"));
125       }));
126       
127       $("td[date=" + this.dateToString(new Date()) + "]", this.tbody).addClass("today");
128       
129       $("td.selectable_day", this.tbody).mouseover(function() { $(this).addClass("hover") });
130       $("td.selectable_day", this.tbody).mouseout(function() { $(this).removeClass("hover") });
131     };
132     
133     $('.selected', this.tbody).removeClass("selected");
134     $('td[date=' + this.selectedDateString + ']', this.tbody).addClass("selected");
135   },
136   
137   selectDate: function(date) {
138     if (typeof(date) == "undefined") {
139       date = this.stringToDate(this.input.val());
140     };
141     if (!date) date = new Date();
142     
143     this.selectedDate = date;
144     this.selectedDateString = this.dateToString(this.selectedDate);
145     this.selectMonth(this.selectedDate);
146   },
147   
148   changeInput: function(dateString) {
149     this.input.val(dateString).change();
150     this.hide();
151   },
152   
153   show: function() {
154     this.rootLayers.css("display", "block");
155     $([window, document.body]).click(this.hideIfClickOutside);
156     this.input.unbind("focus", this.show);
157     $(document.body).keydown(this.keydownHandler);
158     this.setPosition();
159   },
160   
161   hide: function() {
162     this.rootLayers.css("display", "none");
163     $([window, document.body]).unbind("click", this.hideIfClickOutside);
164     this.input.focus(this.show);
165     $(document.body).unbind("keydown", this.keydownHandler);
166   },
167   
168   hideIfClickOutside: function(event) {
169     if (event.target != this.input[0] && !this.insideSelector(event)) {
170       this.hide();
171     };
172   },
173   
174   insideSelector: function(event) {
175     var offset = this.dateSelector.position();
176     offset.right = offset.left + this.dateSelector.outerWidth();
177     offset.bottom = offset.top + this.dateSelector.outerHeight();
178     
179     return event.pageY < offset.bottom &&
180            event.pageY > offset.top &&
181            event.pageX < offset.right &&
182            event.pageX > offset.left;
183   },
184   
185   keydownHandler: function(event) {
186     switch (event.keyCode)
187     {
188       case 9: 
189       case 27: 
190         this.hide();
191         return;
192       break;
193       case 13: 
194         this.changeInput(this.selectedDateString);
195       break;
196       case 33: 
197         this.moveDateMonthBy(event.ctrlKey ? -12 : -1);
198       break;
199       case 34: 
200         this.moveDateMonthBy(event.ctrlKey ? 12 : 1);
201       break;
202       case 38: 
203         this.moveDateBy(-7);
204       break;
205       case 40: 
206         this.moveDateBy(7);
207       break;
208       case 37: 
209         this.moveDateBy(-1);
210       break;
211       case 39: 
212         this.moveDateBy(1);
213       break;
214       default:
215         return;
216     }
217     event.preventDefault();
218   },
219   
220   stringToDate: function(string) {
221     var matches;
222     if (matches = string.match(/^(\d{1,2}) ([^\s]+) (\d{4,4})$/)) {
223       return new Date(matches[3], this.shortMonthNum(matches[2]), matches[1], 12, 00);
224     } else {
225       return null;
226     };
227   },
228   
229   dateToString: function(date) {
230     return date.getDate() + " " + this.short_month_names[date.getMonth()] + " " + date.getFullYear();
231   },
232   
233   setPosition: function() {
234     var offset = this.input.offset();
235     this.rootLayers.css({
236       top: offset.top + this.input.outerHeight(),
237       left: offset.left
238     });
239     
240     if (this.ieframe) {
241       this.ieframe.css({
242         width: this.dateSelector.outerWidth(),
243         height: this.dateSelector.outerHeight()
244       });
245     };
246   },
247   
248   moveDateBy: function(amount) {
249     var newDate = new Date(this.selectedDate.getFullYear(), this.selectedDate.getMonth(), this.selectedDate.getDate() + amount);
250     this.selectDate(newDate);
251   },
252   
253   moveDateMonthBy: function(amount) {
254     var newDate = new Date(this.selectedDate.getFullYear(), this.selectedDate.getMonth() + amount, this.selectedDate.getDate());
255     if (newDate.getMonth() == this.selectedDate.getMonth() + amount + 1) {
256       
257       newDate.setDate(0);
258     };
259     this.selectDate(newDate);
260   },
261   
262   moveMonthBy: function(amount) {
263     var newMonth = new Date(this.currentMonth.getFullYear(), this.currentMonth.getMonth() + amount, this.currentMonth.getDate());
264     this.selectMonth(newMonth);
265   },
266   
267   monthName: function(date) {
268     return this.month_names[date.getMonth()];
269   },
270   
271   bindToObj: function(fn) {
272     var self = this;
273     return function() { return fn.apply(self, arguments) };
274   },
275   
276   bindMethodsToObj: function() {
277     for (var i = 0; i < arguments.length; i++) {
278       this[arguments[i]] = this.bindToObj(this[arguments[i]]);
279     };
280   },
281   
282   indexFor: function(array, value) {
283     for (var i = 0; i < array.length; i++) {
284       if (value == array[i]) return i;
285     };
286   },
287   
288   monthNum: function(month_name) {
289     return this.indexFor(this.month_names, month_name);
290   },
291   
292   shortMonthNum: function(month_name) {
293     return this.indexFor(this.short_month_names, month_name);
294   },
295   
296   shortDayNum: function(day_name) {
297     return this.indexFor(this.short_day_names, day_name);
298   },
299   
300   daysBetween: function(start, end) {
301     start = Date.UTC(start.getFullYear(), start.getMonth(), start.getDate());
302     end = Date.UTC(end.getFullYear(), end.getMonth(), end.getDate());
303     return (end - start) / 86400000;
304   },
305   
306   changeDayTo: function(dayOfWeek, date, direction) {
307     var difference = direction * (Math.abs(date.getDay() - dayOfWeek - (direction * 7)) % 7);
308     return new Date(date.getFullYear(), date.getMonth(), date.getDate() + difference);
309   },
310   
311   rangeStart: function(date) {
312     return this.changeDayTo(this.start_of_week, new Date(date.getFullYear(), date.getMonth()), -1);
313   },
314   
315   rangeEnd: function(date) {
316     return this.changeDayTo((this.start_of_week - 1) % 7, new Date(date.getFullYear(), date.getMonth() + 1, 0), 1);
317   },
318   
319   isFirstDayOfWeek: function(date) {
320     return date.getDay() == this.start_of_week;
321   },
322   
323   isLastDayOfWeek: function(date) {
324     return date.getDay() == (this.start_of_week - 1) % 7;
325   },
326   
327   adjustDays: function(days) {
328     var newDays = [];
329     for (var i = 0; i < days.length; i++) {
330       newDays[i] = days[(i + this.start_of_week) % 7];
331     };
332     return newDays;
333   }
334 };
335
336 $.fn.date_input = function(opts) {
337   return this.each(function() { new DateInput(this, opts); });
338 };
339 $.date_input = { initialize: function(opts) {
340   $("input.date_input").date_input(opts);
341 } };
342
343 return DateInput;
344 })(jQuery);