3 Requires jQuery version: >= 1.2.6
5 Copyright (c) 2007-2008 Jonathan Leighton & Torchbox Ltd
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
16 The above copyright notice and this permission notice shall be
17 included in all copies or substantial portions of the Software.
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.
29 DateInput = (function($) {
31 function DateInput(el, opts) {
32 if (typeof(opts) != "object") opts = {};
33 $.extend(this, DateInput.DEFAULT_OPTS, opts);
36 this.bindMethodsToObj("show", "hide", "hideIfClickOutside", "keydownHandler", "selectDate");
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"],
48 DateInput.prototype = {
50 var monthNav = $('<p class="month_nav">' +
51 '<span class="button prev" title="[Page-Up]">«</span>' +
52 ' <span class="month_name"></span> ' +
53 '<span class="button next" title="[Page-Down]">»</span>' +
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); }));
59 var yearNav = $('<p class="year_nav">' +
60 '<span class="button prev" title="[Ctrl+Page-Up]">«</span>' +
61 ' <span class="year_name"></span> ' +
62 '<span class="button next" title="[Ctrl+Page-Down]">»</span>' +
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); }));
68 var nav = $('<div class="nav"></div>').append(monthNav, yearNav);
70 var tableShell = "<table><thead><tr>";
71 $(this.adjustDays(this.short_day_names)).each(function() {
72 tableShell += "<th>" + this + "</th>";
74 tableShell += "</tr></thead><tbody></tbody></table>";
76 this.dateSelector = this.rootLayers = $('<div class="date_selector"></div>').append(nav, tableShell).insertAfter(this.input);
78 if ($.browser.msie && $.browser.version < 7) {
80 this.ieframe = $('<iframe class="date_selector_ieframe" frameborder="0" src="#"></iframe>').insertBefore(this.dateSelector);
81 this.rootLayers = this.rootLayers.add(this.ieframe);
83 $(".button", nav).mouseover(function() { $(this).addClass("hover") });
84 $(".button", nav).mouseout(function() { $(this).removeClass("hover") });
87 this.tbody = $("tbody", this.dateSelector);
89 this.input.change(this.bindToObj(function() { this.selectDate(); }));
93 selectMonth: function(date) {
94 var newMonth = new Date(date.getFullYear(), date.getMonth(), 1);
96 if (!this.currentMonth || !(this.currentMonth.getFullYear() == newMonth.getFullYear() &&
97 this.currentMonth.getMonth() == newMonth.getMonth())) {
99 this.currentMonth = newMonth;
101 var rangeStart = this.rangeStart(date), rangeEnd = this.rangeEnd(date);
102 var numDays = this.daysBetween(rangeStart, rangeEnd);
105 for (var i = 0; i <= numDays; i++) {
106 var currentDay = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate() + i, 12, 00);
108 if (this.isFirstDayOfWeek(currentDay)) dayCells += "<tr>";
110 if (currentDay.getMonth() == date.getMonth()) {
111 dayCells += '<td class="selectable_day" date="' + this.dateToString(currentDay) + '">' + currentDay.getDate() + '</td>';
113 dayCells += '<td class="unselected_month" date="' + this.dateToString(currentDay) + '">' + currentDay.getDate() + '</td>';
116 if (this.isLastDayOfWeek(currentDay)) dayCells += "</tr>";
118 this.tbody.empty().append(dayCells);
120 this.monthNameSpan.empty().append(this.monthName(date));
121 this.yearNameSpan.empty().append(this.currentMonth.getFullYear());
123 $(".selectable_day", this.tbody).click(this.bindToObj(function(event) {
124 this.changeInput($(event.target).attr("date"));
127 $("td[date=" + this.dateToString(new Date()) + "]", this.tbody).addClass("today");
129 $("td.selectable_day", this.tbody).mouseover(function() { $(this).addClass("hover") });
130 $("td.selectable_day", this.tbody).mouseout(function() { $(this).removeClass("hover") });
133 $('.selected', this.tbody).removeClass("selected");
134 $('td[date=' + this.selectedDateString + ']', this.tbody).addClass("selected");
137 selectDate: function(date) {
138 if (typeof(date) == "undefined") {
139 date = this.stringToDate(this.input.val());
141 if (!date) date = new Date();
143 this.selectedDate = date;
144 this.selectedDateString = this.dateToString(this.selectedDate);
145 this.selectMonth(this.selectedDate);
148 changeInput: function(dateString) {
149 this.input.val(dateString).change();
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);
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);
168 hideIfClickOutside: function(event) {
169 if (event.target != this.input[0] && !this.insideSelector(event)) {
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();
179 return event.pageY < offset.bottom &&
180 event.pageY > offset.top &&
181 event.pageX < offset.right &&
182 event.pageX > offset.left;
185 keydownHandler: function(event) {
186 switch (event.keyCode)
194 this.changeInput(this.selectedDateString);
197 this.moveDateMonthBy(event.ctrlKey ? -12 : -1);
200 this.moveDateMonthBy(event.ctrlKey ? 12 : 1);
217 event.preventDefault();
220 stringToDate: function(string) {
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);
229 dateToString: function(date) {
230 return date.getDate() + " " + this.short_month_names[date.getMonth()] + " " + date.getFullYear();
233 setPosition: function() {
234 var offset = this.input.offset();
235 this.rootLayers.css({
236 top: offset.top + this.input.outerHeight(),
242 width: this.dateSelector.outerWidth(),
243 height: this.dateSelector.outerHeight()
248 moveDateBy: function(amount) {
249 var newDate = new Date(this.selectedDate.getFullYear(), this.selectedDate.getMonth(), this.selectedDate.getDate() + amount);
250 this.selectDate(newDate);
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) {
259 this.selectDate(newDate);
262 moveMonthBy: function(amount) {
263 var newMonth = new Date(this.currentMonth.getFullYear(), this.currentMonth.getMonth() + amount, this.currentMonth.getDate());
264 this.selectMonth(newMonth);
267 monthName: function(date) {
268 return this.month_names[date.getMonth()];
271 bindToObj: function(fn) {
273 return function() { return fn.apply(self, arguments) };
276 bindMethodsToObj: function() {
277 for (var i = 0; i < arguments.length; i++) {
278 this[arguments[i]] = this.bindToObj(this[arguments[i]]);
282 indexFor: function(array, value) {
283 for (var i = 0; i < array.length; i++) {
284 if (value == array[i]) return i;
288 monthNum: function(month_name) {
289 return this.indexFor(this.month_names, month_name);
292 shortMonthNum: function(month_name) {
293 return this.indexFor(this.short_month_names, month_name);
296 shortDayNum: function(day_name) {
297 return this.indexFor(this.short_day_names, day_name);
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;
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);
311 rangeStart: function(date) {
312 return this.changeDayTo(this.start_of_week, new Date(date.getFullYear(), date.getMonth()), -1);
315 rangeEnd: function(date) {
316 return this.changeDayTo((this.start_of_week - 1) % 7, new Date(date.getFullYear(), date.getMonth() + 1, 0), 1);
319 isFirstDayOfWeek: function(date) {
320 return date.getDay() == this.start_of_week;
323 isLastDayOfWeek: function(date) {
324 return date.getDay() == (this.start_of_week - 1) % 7;
327 adjustDays: function(days) {
329 for (var i = 0; i < days.length; i++) {
330 newDays[i] = days[(i + this.start_of_week) % 7];
336 $.fn.date_input = function(opts) {
337 return this.each(function() { new DateInput(this, opts); });
339 $.date_input = { initialize: function(opts) {
340 $("input.date_input").date_input(opts);