/** the script only works on "input [type=text]" **/ // don't declare anything out here in the global namespace (function($) { // create private scope (inside you can use $ instead of jQuery) // functions and vars declared here are effectively 'singletons'. there will be only a single // instance of them. so this is a good place to declare any immutable items or stateless // functions. for example: var today = new Date(); // used in defaults var months = 'Január,Február,Marec,Apríl,Máj,Jún,Júl,August,September,Október,November,December'.split(','); var monthlengths = '31,28,31,30,31,30,31,31,30,31,30,31'.split(','); var dateRegEx = /^\d{1,2}\/\d{1,2}\/\d{2}|\d{4}$/; var yearRegEx = /^\d{4,4}$/; // next, declare the plugin function $.fn.simpleDatepicker = function(options) { // functions and vars declared here are created each time your plugn function is invoked // you could probably refactor your 'build', 'load_month', etc, functions to be passed // the DOM element from below var opts = jQuery.extend({}, jQuery.fn.simpleDatepicker.defaults, options); // replaces a date string with a date object in opts.startdate and opts.enddate, if one exists // populates two new properties with a ready-to-use year: opts.startyear and opts.endyear setupYearRange(); /** extracts and setup a valid year range from the opts object **/ function setupYearRange () { var startyear, endyear; if (opts.startdate.constructor == Date) { startyear = opts.startdate.getFullYear(); } else if (opts.startdate) { if (yearRegEx.test(opts.startdate)) { startyear = opts.startdate; } else if (dateRegEx.test(opts.startdate)) { opts.startdate = new Date(opts.startdate); startyear = opts.startdate.getFullYear(); } else { startyear = today.getFullYear(); } } else { startyear = today.getFullYear(); } opts.startyear = startyear; if (opts.enddate.constructor == Date) { endyear = opts.enddate.getFullYear(); } else if (opts.enddate) { if (yearRegEx.test(opts.enddate)) { endyear = opts.enddate; } else if (dateRegEx.test(opts.enddate)) { opts.enddate = new Date(opts.enddate); endyear = opts.enddate.getFullYear(); } else { endyear = today.getFullYear(); } } else { endyear = today.getFullYear(); } opts.endyear = endyear; } /** HTML factory for the actual datepicker table element **/ // has to read the year range so it can setup the correct years in our HTML '; for (var i in months) monthselect += ''; monthselect += ''; // year select field var yearselect = ''; jQuery("thead",table).append('« '+monthselect+yearselect+' »'); jQuery("thead",table).append('NPUSŠPS'); jQuery("tfoot",table).append(' '); for (var i = 0; i < 6; i++) jQuery("tbody",table).append(''); return table; } /** get the real position of the input (well, anything really) **/ //http://www.quirksmode.org/js/findpos.html function findPosition (obj) { var curleft = curtop = 0; if (obj.offsetParent) { do { curleft += obj.offsetLeft; curtop += obj.offsetTop; } while (obj = obj.offsetParent); return [curleft,curtop]; } else { return false; } } /** load the initial date and handle all date-navigation **/ // initial calendar load (e is null) // prevMonth & nextMonth buttons // onchange for the select fields function loadMonth (e, el, datepicker, chosendate) { // reference our years for the nextMonth and prevMonth buttons var mo = jQuery("select[name=month]", datepicker).get(0).selectedIndex; var yr = jQuery("select[name=year]", datepicker).get(0).selectedIndex; var yrs = jQuery("select[name=year] option", datepicker).get().length; // first try to process buttons that may change the month we're on if (e && jQuery(e.target).hasClass('prevMonth')) { if (0 == mo && yr) { yr -= 1; mo = 11; jQuery("select[name=month]", datepicker).get(0).selectedIndex = 11; jQuery("select[name=year]", datepicker).get(0).selectedIndex = yr; } else { mo -= 1; jQuery("select[name=month]", datepicker).get(0).selectedIndex = mo; } } else if (e && jQuery(e.target).hasClass('nextMonth')) { if (11 == mo && yr + 1 < yrs) { yr += 1; mo = 0; jQuery("select[name=month]", datepicker).get(0).selectedIndex = 0; jQuery("select[name=year]", datepicker).get(0).selectedIndex = yr; } else { mo += 1; jQuery("select[name=month]", datepicker).get(0).selectedIndex = mo; } } // maybe hide buttons if (0 == mo && !yr) jQuery("span.prevMonth", datepicker).hide(); else jQuery("span.prevMonth", datepicker).show(); if (yr + 1 == yrs && 11 == mo) jQuery("span.nextMonth", datepicker).hide(); else jQuery("span.nextMonth", datepicker).show(); // clear the old cells var cells = jQuery("tbody td", datepicker).unbind().empty().removeClass('date'); // figure out what month and year to load var m = jQuery("select[name=month]", datepicker).val(); var y = jQuery("select[name=year]", datepicker).val(); var d = new Date(y, m, 1); var startindex = d.getDay(); var numdays = monthlengths[m]; // http://en.wikipedia.org/wiki/Leap_year if (1 == m && ((y%4 == 0 && y%100 != 0) || y%400 == 0)) numdays = 29; // test for end dates (instead of just a year range) if (opts.startdate.constructor == Date) { var startMonth = opts.startdate.getMonth(); var startDate = opts.startdate.getDate(); } if (opts.enddate.constructor == Date) { var endMonth = opts.enddate.getMonth(); var endDate = opts.enddate.getDate(); } // walk through the index and populate each cell, binding events too for (var i = 0; i < numdays; i++) { var cell = jQuery(cells.get(i+startindex)).removeClass('chosen'); // test that the date falls within a range, if we have a range if ( (yr || ((!startDate && !startMonth) || ((i+1 >= startDate && mo == startMonth) || mo > startMonth))) && (yr + 1 < yrs || ((!endDate && !endMonth) || ((i+1 <= endDate && mo == endMonth) || mo < endMonth)))) { cell .text(i+1) .addClass('date') .hover( function () { jQuery(this).addClass('over'); }, function () { jQuery(this).removeClass('over'); }) .click(function () { var chosenDateObj = new Date(jQuery("select[name=year]", datepicker).val(), jQuery("select[name=month]", datepicker).val(), jQuery(this).text()); closeIt(el, datepicker, chosenDateObj); }); // highlight the previous chosen date if (i+1 == chosendate.getDate() && m == chosendate.getMonth() && y == chosendate.getFullYear()) cell.addClass('chosen'); } } } /** closes the datepicker **/ // sets the currently matched input element's value to the date, if one is available // remove the table element from the DOM // indicate that there is no datepicker for the currently matched input element function closeIt (el, datepicker, dateObj) { if (dateObj && dateObj.constructor == Date) el.val(jQuery.fn.simpleDatepicker.formatOutput(dateObj)); datepicker.remove(); datepicker = null; jQuery.data(el.get(0), "simpleDatepicker", { hasDatepicker : false }); } // iterate the matched nodeset return this.each(function() { // functions and vars declared here are created for each matched element. so if // your functions need to manage or access per-node state you can defined them // here and use $this to get at the DOM element if ( jQuery(this).is('input') && 'text' == jQuery(this).attr('type')) { var datepicker; jQuery.data(jQuery(this).get(0), "simpleDatepicker", { hasDatepicker : false }); // open a datepicker on the click event jQuery(this).click(function (ev) { var $this = jQuery(ev.target); if (false == jQuery.data($this.get(0), "simpleDatepicker").hasDatepicker) { // store data telling us there is already a datepicker jQuery.data($this.get(0), "simpleDatepicker", { hasDatepicker : true }); // validate the form's initial content for a date var initialDate = $this.val(); if (initialDate && dateRegEx.test(initialDate)) { var chosendate = new Date(initialDate); } else if (opts.chosendate.constructor == Date) { var chosendate = opts.chosendate; } else if (opts.chosendate) { var chosendate = new Date(opts.chosendate); } else { var chosendate = today; } // insert the datepicker in the DOM datepicker = newDatepickerHTML(); jQuery("body").prepend(datepicker); // position the datepicker var elPos = findPosition($this.get(0)); var x = (parseInt(opts.x) ? parseInt(opts.x) : 0) + elPos[0]; var y = (parseInt(opts.y) ? parseInt(opts.y) : 0) + elPos[1]; jQuery(datepicker).css({ position: 'absolute', left: x, top: y }); // bind events to the table controls jQuery("span", datepicker).css("cursor","pointer"); jQuery("select", datepicker).bind('change', function () { loadMonth (null, $this, datepicker, chosendate); }); jQuery("span.prevMonth", datepicker).click(function (e) { loadMonth (e, $this, datepicker, chosendate); }); jQuery("span.nextMonth", datepicker).click(function (e) { loadMonth (e, $this, datepicker, chosendate); }); jQuery("span.today", datepicker).click(function () { closeIt($this, datepicker, new Date()); }); jQuery("span.close", datepicker).click(function () { closeIt($this, datepicker); }); // set the initial values for the month and year select fields // and load the first month jQuery("select[name=month]", datepicker).get(0).selectedIndex = chosendate.getMonth(); jQuery("select[name=year]", datepicker).get(0).selectedIndex = Math.max(0, chosendate.getFullYear() - opts.startyear); loadMonth(null, $this, datepicker, chosendate); } }); } }); }; // finally, I like to expose default plugin options as public so they can be manipulated. one // way to do this is to add a property to the already-public plugin fn jQuery.fn.simpleDatepicker.formatOutput = function (dateObj) { return (dateObj.getMonth() + 1) + "/" + dateObj.getDate() + "/" + dateObj.getFullYear(); }; jQuery.fn.simpleDatepicker.defaults = { // date string matching /^\d{1,2}\/\d{1,2}\/\d{2}|\d{4}$/ chosendate : today, // date string matching /^\d{1,2}\/\d{1,2}\/\d{2}|\d{4}$/ // or four digit year startdate : today.getFullYear(), enddate : today.getFullYear() + 1, // offset from the top left corner of the input element x : 18, // must be in px y : 18 // must be in px }; })(jQuery);