0
|
1 |
/** |
|
2 |
the script only works on "input [type=text]" |
|
3 |
|
|
4 |
**/ |
|
5 |
|
|
6 |
// don't declare anything out here in the global namespace |
|
7 |
|
|
8 |
(function($) { // create private scope (inside you can use $ instead of jQuery) |
|
9 |
|
|
10 |
// functions and vars declared here are effectively 'singletons'. there will be only a single |
|
11 |
// instance of them. so this is a good place to declare any immutable items or stateless |
|
12 |
// functions. for example: |
|
13 |
|
|
14 |
var today = new Date(); // used in defaults |
|
15 |
var months = 'January,February,March,April,May,June,July,August,September,October,November,December'.split(','); |
|
16 |
var monthlengths = '31,28,31,30,31,30,31,31,30,31,30,31'.split(','); |
|
17 |
var dateRegEx = /^\d{1,2}\/\d{1,2}\/\d{2}|\d{4}$/; |
|
18 |
var yearRegEx = /^\d{4,4}$/; |
|
19 |
|
|
20 |
// next, declare the plugin function |
|
21 |
$.fn.simpleDatepicker = function(options) { |
|
22 |
|
|
23 |
// functions and vars declared here are created each time your plugn function is invoked |
|
24 |
|
|
25 |
// you could probably refactor your 'build', 'load_month', etc, functions to be passed |
|
26 |
// the DOM element from below |
|
27 |
|
|
28 |
var opts = jQuery.extend({}, jQuery.fn.simpleDatepicker.defaults, options); |
|
29 |
|
|
30 |
// replaces a date string with a date object in opts.startdate and opts.enddate, if one exists |
|
31 |
// populates two new properties with a ready-to-use year: opts.startyear and opts.endyear |
|
32 |
|
|
33 |
setupYearRange(); |
|
34 |
/** extracts and setup a valid year range from the opts object **/ |
|
35 |
function setupYearRange () { |
|
36 |
|
|
37 |
var startyear, endyear; |
|
38 |
if (opts.startdate.constructor == Date) { |
|
39 |
startyear = opts.startdate.getFullYear(); |
|
40 |
} else if (opts.startdate) { |
|
41 |
if (yearRegEx.test(opts.startdate)) { |
|
42 |
startyear = opts.startdate; |
|
43 |
} else if (dateRegEx.test(opts.startdate)) { |
|
44 |
opts.startdate = new Date(opts.startdate); |
|
45 |
startyear = opts.startdate.getFullYear(); |
|
46 |
} else { |
|
47 |
startyear = today.getFullYear(); |
|
48 |
} |
|
49 |
} else { |
|
50 |
startyear = today.getFullYear(); |
|
51 |
} |
|
52 |
opts.startyear = startyear; |
|
53 |
|
|
54 |
if (opts.enddate.constructor == Date) { |
|
55 |
endyear = opts.enddate.getFullYear(); |
|
56 |
} else if (opts.enddate) { |
|
57 |
if (yearRegEx.test(opts.enddate)) { |
|
58 |
endyear = opts.enddate; |
|
59 |
} else if (dateRegEx.test(opts.enddate)) { |
|
60 |
opts.enddate = new Date(opts.enddate); |
|
61 |
endyear = opts.enddate.getFullYear(); |
|
62 |
} else { |
|
63 |
endyear = today.getFullYear(); |
|
64 |
} |
|
65 |
} else { |
|
66 |
endyear = today.getFullYear(); |
|
67 |
} |
|
68 |
opts.endyear = endyear; |
|
69 |
} |
|
70 |
|
|
71 |
/** HTML factory for the actual datepicker table element **/ |
|
72 |
// has to read the year range so it can setup the correct years in our HTML <select> |
|
73 |
function newDatepickerHTML () { |
|
74 |
|
|
75 |
var years = []; |
|
76 |
|
|
77 |
// process year range into an array |
|
78 |
for (var i = 0; i <= opts.endyear - opts.startyear; i ++) years[i] = opts.startyear + i; |
|
79 |
|
|
80 |
// build the table structure |
|
81 |
var table = jQuery('<table class="datepicker" cellpadding="0" cellspacing="0"></table>'); |
|
82 |
table.append('<thead></thead>'); |
|
83 |
table.append('<tfoot></tfoot>'); |
|
84 |
table.append('<tbody></tbody>'); |
|
85 |
|
|
86 |
// month select field |
|
87 |
var monthselect = '<select name="month">'; |
|
88 |
for (var i in months) monthselect += '<option value="'+i+'">'+months[i]+'</option>'; |
|
89 |
monthselect += '</select>'; |
|
90 |
|
|
91 |
// year select field |
|
92 |
var yearselect = '<select name="year">'; |
|
93 |
for (var i in years) yearselect += '<option>'+years[i]+'</option>'; |
|
94 |
yearselect += '</select>'; |
|
95 |
|
|
96 |
jQuery("thead",table).append('<tr class="controls"><th colspan="7"><span class="prevMonth">«</span> '+monthselect+yearselect+' <span class="nextMonth">»</span></th></tr>'); |
|
97 |
jQuery("thead",table).append('<tr class="days"><th>S</th><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th></tr>'); |
|
98 |
jQuery("tfoot",table).append('<tr><td colspan="2"><span class="today">today</span></td><td colspan="3"> </td><td colspan="2"><span class="close">close</span></td></tr>'); |
|
99 |
for (var i = 0; i < 6; i++) jQuery("tbody",table).append('<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>'); |
|
100 |
return table; |
|
101 |
} |
|
102 |
|
|
103 |
/** get the real position of the input (well, anything really) **/ |
|
104 |
//http://www.quirksmode.org/js/findpos.html |
|
105 |
function findPosition (obj) { |
|
106 |
var curleft = curtop = 0; |
|
107 |
if (obj.offsetParent) { |
|
108 |
do { |
|
109 |
curleft += obj.offsetLeft; |
|
110 |
curtop += obj.offsetTop; |
|
111 |
} while (obj = obj.offsetParent); |
|
112 |
return [curleft,curtop]; |
|
113 |
} else { |
|
114 |
return false; |
|
115 |
} |
|
116 |
} |
|
117 |
|
|
118 |
/** load the initial date and handle all date-navigation **/ |
|
119 |
// initial calendar load (e is null) |
|
120 |
// prevMonth & nextMonth buttons |
|
121 |
// onchange for the select fields |
|
122 |
function loadMonth (e, el, datepicker, chosendate) { |
|
123 |
|
|
124 |
// reference our years for the nextMonth and prevMonth buttons |
|
125 |
var mo = jQuery("select[name=month]", datepicker).get(0).selectedIndex; |
|
126 |
var yr = jQuery("select[name=year]", datepicker).get(0).selectedIndex; |
|
127 |
var yrs = jQuery("select[name=year] option", datepicker).get().length; |
|
128 |
|
|
129 |
// first try to process buttons that may change the month we're on |
|
130 |
if (e && jQuery(e.target).hasClass('prevMonth')) { |
|
131 |
if (0 == mo && yr) { |
|
132 |
yr -= 1; mo = 11; |
|
133 |
jQuery("select[name=month]", datepicker).get(0).selectedIndex = 11; |
|
134 |
jQuery("select[name=year]", datepicker).get(0).selectedIndex = yr; |
|
135 |
} else { |
|
136 |
mo -= 1; |
|
137 |
jQuery("select[name=month]", datepicker).get(0).selectedIndex = mo; |
|
138 |
} |
|
139 |
} else if (e && jQuery(e.target).hasClass('nextMonth')) { |
|
140 |
if (11 == mo && yr + 1 < yrs) { |
|
141 |
yr += 1; mo = 0; |
|
142 |
jQuery("select[name=month]", datepicker).get(0).selectedIndex = 0; |
|
143 |
jQuery("select[name=year]", datepicker).get(0).selectedIndex = yr; |
|
144 |
} else { |
|
145 |
mo += 1; |
|
146 |
jQuery("select[name=month]", datepicker).get(0).selectedIndex = mo; |
|
147 |
} |
|
148 |
} |
|
149 |
|
|
150 |
// maybe hide buttons |
|
151 |
if (0 == mo && !yr) jQuery("span.prevMonth", datepicker).hide(); |
|
152 |
else jQuery("span.prevMonth", datepicker).show(); |
|
153 |
if (yr + 1 == yrs && 11 == mo) jQuery("span.nextMonth", datepicker).hide(); |
|
154 |
else jQuery("span.nextMonth", datepicker).show(); |
|
155 |
|
|
156 |
// clear the old cells |
|
157 |
var cells = jQuery("tbody td", datepicker).unbind().empty().removeClass('date'); |
|
158 |
|
|
159 |
// figure out what month and year to load |
|
160 |
var m = jQuery("select[name=month]", datepicker).val(); |
|
161 |
var y = jQuery("select[name=year]", datepicker).val(); |
|
162 |
var d = new Date(y, m, 1); |
|
163 |
var startindex = d.getDay(); |
|
164 |
var numdays = monthlengths[m]; |
|
165 |
|
|
166 |
// http://en.wikipedia.org/wiki/Leap_year |
|
167 |
if (1 == m && ((y%4 == 0 && y%100 != 0) || y%400 == 0)) numdays = 29; |
|
168 |
|
|
169 |
// test for end dates (instead of just a year range) |
|
170 |
if (opts.startdate.constructor == Date) { |
|
171 |
var startMonth = opts.startdate.getMonth(); |
|
172 |
var startDate = opts.startdate.getDate(); |
|
173 |
} |
|
174 |
if (opts.enddate.constructor == Date) { |
|
175 |
var endMonth = opts.enddate.getMonth(); |
|
176 |
var endDate = opts.enddate.getDate(); |
|
177 |
} |
|
178 |
|
|
179 |
// walk through the index and populate each cell, binding events too |
|
180 |
for (var i = 0; i < numdays; i++) { |
|
181 |
|
|
182 |
var cell = jQuery(cells.get(i+startindex)).removeClass('chosen'); |
|
183 |
|
|
184 |
// test that the date falls within a range, if we have a range |
|
185 |
if ( |
|
186 |
(yr || ((!startDate && !startMonth) || ((i+1 >= startDate && mo == startMonth) || mo > startMonth))) && |
|
187 |
(yr + 1 < yrs || ((!endDate && !endMonth) || ((i+1 <= endDate && mo == endMonth) || mo < endMonth)))) { |
|
188 |
|
|
189 |
cell |
|
190 |
.text(i+1) |
|
191 |
.addClass('date') |
|
192 |
.hover( |
|
193 |
function () { jQuery(this).addClass('over'); }, |
|
194 |
function () { jQuery(this).removeClass('over'); }) |
|
195 |
.click(function () { |
|
196 |
var chosenDateObj = new Date(jQuery("select[name=year]", datepicker).val(), jQuery("select[name=month]", datepicker).val(), jQuery(this).text()); |
|
197 |
closeIt(el, datepicker, chosenDateObj); |
|
198 |
}); |
|
199 |
|
|
200 |
// highlight the previous chosen date |
|
201 |
if (i+1 == chosendate.getDate() && m == chosendate.getMonth() && y == chosendate.getFullYear()) cell.addClass('chosen'); |
|
202 |
} |
|
203 |
} |
|
204 |
} |
|
205 |
|
|
206 |
/** closes the datepicker **/ |
|
207 |
// sets the currently matched input element's value to the date, if one is available |
|
208 |
// remove the table element from the DOM |
|
209 |
// indicate that there is no datepicker for the currently matched input element |
|
210 |
function closeIt (el, datepicker, dateObj) { |
|
211 |
if (dateObj && dateObj.constructor == Date) |
|
212 |
el.val(jQuery.fn.simpleDatepicker.formatOutput(dateObj)); |
|
213 |
datepicker.remove(); |
|
214 |
datepicker = null; |
|
215 |
jQuery.data(el.get(0), "simpleDatepicker", { hasDatepicker : false }); |
|
216 |
} |
|
217 |
|
|
218 |
// iterate the matched nodeset |
|
219 |
return this.each(function() { |
|
220 |
|
|
221 |
// functions and vars declared here are created for each matched element. so if |
|
222 |
// your functions need to manage or access per-node state you can defined them |
|
223 |
// here and use $this to get at the DOM element |
|
224 |
|
|
225 |
if ( jQuery(this).is('input') && 'text' == jQuery(this).attr('type')) { |
|
226 |
|
|
227 |
var datepicker; |
|
228 |
jQuery.data(jQuery(this).get(0), "simpleDatepicker", { hasDatepicker : false }); |
|
229 |
|
|
230 |
// open a datepicker on the click event |
|
231 |
jQuery(this).click(function (ev) { |
|
232 |
|
|
233 |
var $this = jQuery(ev.target); |
|
234 |
|
|
235 |
if (false == jQuery.data($this.get(0), "simpleDatepicker").hasDatepicker) { |
|
236 |
|
|
237 |
// store data telling us there is already a datepicker |
|
238 |
jQuery.data($this.get(0), "simpleDatepicker", { hasDatepicker : true }); |
|
239 |
|
|
240 |
// validate the form's initial content for a date |
|
241 |
var initialDate = $this.val(); |
|
242 |
|
|
243 |
if (initialDate && dateRegEx.test(initialDate)) { |
|
244 |
var chosendate = new Date(initialDate); |
|
245 |
} else if (opts.chosendate.constructor == Date) { |
|
246 |
var chosendate = opts.chosendate; |
|
247 |
} else if (opts.chosendate) { |
|
248 |
var chosendate = new Date(opts.chosendate); |
|
249 |
} else { |
|
250 |
var chosendate = today; |
|
251 |
} |
|
252 |
|
|
253 |
// insert the datepicker in the DOM |
|
254 |
datepicker = newDatepickerHTML(); |
|
255 |
jQuery("body").prepend(datepicker); |
|
256 |
|
|
257 |
// position the datepicker |
|
258 |
var elPos = findPosition($this.get(0)); |
|
259 |
var x = (parseInt(opts.x) ? parseInt(opts.x) : 0) + elPos[0]; |
|
260 |
var y = (parseInt(opts.y) ? parseInt(opts.y) : 0) + elPos[1]; |
|
261 |
jQuery(datepicker).css({ position: 'absolute', left: x, top: y }); |
|
262 |
|
|
263 |
// bind events to the table controls |
|
264 |
jQuery("span", datepicker).css("cursor","pointer"); |
|
265 |
jQuery("select", datepicker).bind('change', function () { loadMonth (null, $this, datepicker, chosendate); }); |
|
266 |
jQuery("span.prevMonth", datepicker).click(function (e) { loadMonth (e, $this, datepicker, chosendate); }); |
|
267 |
jQuery("span.nextMonth", datepicker).click(function (e) { loadMonth (e, $this, datepicker, chosendate); }); |
|
268 |
jQuery("span.today", datepicker).click(function () { closeIt($this, datepicker, new Date()); }); |
|
269 |
jQuery("span.close", datepicker).click(function () { closeIt($this, datepicker); }); |
|
270 |
|
|
271 |
// set the initial values for the month and year select fields |
|
272 |
// and load the first month |
|
273 |
jQuery("select[name=month]", datepicker).get(0).selectedIndex = chosendate.getMonth(); |
|
274 |
jQuery("select[name=year]", datepicker).get(0).selectedIndex = Math.max(0, chosendate.getFullYear() - opts.startyear); |
|
275 |
loadMonth(null, $this, datepicker, chosendate); |
|
276 |
} |
|
277 |
|
|
278 |
}); |
|
279 |
} |
|
280 |
|
|
281 |
}); |
|
282 |
|
|
283 |
}; |
|
284 |
|
|
285 |
// finally, I like to expose default plugin options as public so they can be manipulated. one |
|
286 |
// way to do this is to add a property to the already-public plugin fn |
|
287 |
|
|
288 |
jQuery.fn.simpleDatepicker.formatOutput = function (dateObj) { |
|
289 |
return (dateObj.getMonth() + 1) + "/" + dateObj.getDate() + "/" + dateObj.getFullYear(); |
|
290 |
}; |
|
291 |
|
|
292 |
jQuery.fn.simpleDatepicker.defaults = { |
|
293 |
// date string matching /^\d{1,2}\/\d{1,2}\/\d{2}|\d{4}$/ |
|
294 |
chosendate : today, |
|
295 |
|
|
296 |
// date string matching /^\d{1,2}\/\d{1,2}\/\d{2}|\d{4}$/ |
|
297 |
// or four digit year |
|
298 |
startdate : today.getFullYear(), |
|
299 |
enddate : today.getFullYear() + 1, |
|
300 |
|
|
301 |
// offset from the top left corner of the input element |
|
302 |
x : 18, // must be in px |
|
303 |
y : 18 // must be in px |
|
304 |
}; |
|
305 |
|
|
306 |
})(jQuery); |