UNPKG

@coveord/plasma-style

Version:

Yet another CSS framework - but it's awesome & built by Coveo.

943 lines (919 loc) 57 kB
/** * DatePicker 1.0.0 * * A jQuery-based DatePicker that provides an easy way of creating both single * and multi-viewed calendars capable of accepting single, range, and multiple * selected dates. Easily styled with two example styles provided: an attractive * 'dark' style, and a Google Analytics-like 'clean' style. * * View project page for Examples and Documentation: * http://foxrunsoftware.github.com/DatePicker/ * * This project is distinct from and not affiliated with the jquery.ui.datepicker. * * Copyright 2012, Justin Stern (www.foxrunsoftware.net) * Dual licensed under the MIT and GPL Version 2 licenses. * * Based on Work by Original Author: Stefan Petre www.eyecon.ro * * Depends: * jquery.js */ (function ($) { var DatePicker = (function () { var ids = {}, views = { years: 'datepickerViewYears', moths: 'datepickerViewMonths', days: 'datepickerViewDays', }, tpl = { wrapper: '<div class="datepicker"><div class="datepickerBorderT" /><div class="datepickerBorderB" /><div class="datepickerBorderL" /><div class="datepickerBorderR" /><div class="datepickerBorderTL" /><div class="datepickerBorderTR" /><div class="datepickerBorderBL" /><div class="datepickerBorderBR" /><div class="datepickerContainer"><table class="datepicker-table" cellspacing="0" cellpadding="0"><tbody><tr></tr></tbody></table></div></div>', head: [ '<td class="datepickerBlock">', '<table class="datepicker-table" cellspacing="0" cellpadding="0">', '<thead>', '<tr>', '<th colspan="7"><a class="datepickerGoPrev" href="#"><span><%=prev%></span></a>', '<a class="datepickerMonth" href="#"><span></span></a>', '<a class="datepickerGoNext" href="#"><span><%=next%></span></a></th>', '</tr>', '<tr class="datepickerDoW">', '<th><span><%=day1%></span></th>', '<th><span><%=day2%></span></th>', '<th><span><%=day3%></span></th>', '<th><span><%=day4%></span></th>', '<th><span><%=day5%></span></th>', '<th><span><%=day6%></span></th>', '<th><span><%=day7%></span></th>', '</tr>', '</thead>', '</table></td>', ], space: '<td class="datepickerSpace"><div></div></td>', days: [ '<tbody class="datepickerDays">', '<tr>', '<td class="<%=weeks[0].days[0].classname%>"><a href="#"><span><%=weeks[0].days[0].text%></span></a></td>', '<td class="<%=weeks[0].days[1].classname%>"><a href="#"><span><%=weeks[0].days[1].text%></span></a></td>', '<td class="<%=weeks[0].days[2].classname%>"><a href="#"><span><%=weeks[0].days[2].text%></span></a></td>', '<td class="<%=weeks[0].days[3].classname%>"><a href="#"><span><%=weeks[0].days[3].text%></span></a></td>', '<td class="<%=weeks[0].days[4].classname%>"><a href="#"><span><%=weeks[0].days[4].text%></span></a></td>', '<td class="<%=weeks[0].days[5].classname%>"><a href="#"><span><%=weeks[0].days[5].text%></span></a></td>', '<td class="<%=weeks[0].days[6].classname%>"><a href="#"><span><%=weeks[0].days[6].text%></span></a></td>', '</tr>', '<tr>', '<td class="<%=weeks[1].days[0].classname%>"><a href="#"><span><%=weeks[1].days[0].text%></span></a></td>', '<td class="<%=weeks[1].days[1].classname%>"><a href="#"><span><%=weeks[1].days[1].text%></span></a></td>', '<td class="<%=weeks[1].days[2].classname%>"><a href="#"><span><%=weeks[1].days[2].text%></span></a></td>', '<td class="<%=weeks[1].days[3].classname%>"><a href="#"><span><%=weeks[1].days[3].text%></span></a></td>', '<td class="<%=weeks[1].days[4].classname%>"><a href="#"><span><%=weeks[1].days[4].text%></span></a></td>', '<td class="<%=weeks[1].days[5].classname%>"><a href="#"><span><%=weeks[1].days[5].text%></span></a></td>', '<td class="<%=weeks[1].days[6].classname%>"><a href="#"><span><%=weeks[1].days[6].text%></span></a></td>', '</tr>', '<tr>', '<td class="<%=weeks[2].days[0].classname%>"><a href="#"><span><%=weeks[2].days[0].text%></span></a></td>', '<td class="<%=weeks[2].days[1].classname%>"><a href="#"><span><%=weeks[2].days[1].text%></span></a></td>', '<td class="<%=weeks[2].days[2].classname%>"><a href="#"><span><%=weeks[2].days[2].text%></span></a></td>', '<td class="<%=weeks[2].days[3].classname%>"><a href="#"><span><%=weeks[2].days[3].text%></span></a></td>', '<td class="<%=weeks[2].days[4].classname%>"><a href="#"><span><%=weeks[2].days[4].text%></span></a></td>', '<td class="<%=weeks[2].days[5].classname%>"><a href="#"><span><%=weeks[2].days[5].text%></span></a></td>', '<td class="<%=weeks[2].days[6].classname%>"><a href="#"><span><%=weeks[2].days[6].text%></span></a></td>', '</tr>', '<tr>', '<td class="<%=weeks[3].days[0].classname%>"><a href="#"><span><%=weeks[3].days[0].text%></span></a></td>', '<td class="<%=weeks[3].days[1].classname%>"><a href="#"><span><%=weeks[3].days[1].text%></span></a></td>', '<td class="<%=weeks[3].days[2].classname%>"><a href="#"><span><%=weeks[3].days[2].text%></span></a></td>', '<td class="<%=weeks[3].days[3].classname%>"><a href="#"><span><%=weeks[3].days[3].text%></span></a></td>', '<td class="<%=weeks[3].days[4].classname%>"><a href="#"><span><%=weeks[3].days[4].text%></span></a></td>', '<td class="<%=weeks[3].days[5].classname%>"><a href="#"><span><%=weeks[3].days[5].text%></span></a></td>', '<td class="<%=weeks[3].days[6].classname%>"><a href="#"><span><%=weeks[3].days[6].text%></span></a></td>', '</tr>', '<tr>', '<td class="<%=weeks[4].days[0].classname%>"><a href="#"><span><%=weeks[4].days[0].text%></span></a></td>', '<td class="<%=weeks[4].days[1].classname%>"><a href="#"><span><%=weeks[4].days[1].text%></span></a></td>', '<td class="<%=weeks[4].days[2].classname%>"><a href="#"><span><%=weeks[4].days[2].text%></span></a></td>', '<td class="<%=weeks[4].days[3].classname%>"><a href="#"><span><%=weeks[4].days[3].text%></span></a></td>', '<td class="<%=weeks[4].days[4].classname%>"><a href="#"><span><%=weeks[4].days[4].text%></span></a></td>', '<td class="<%=weeks[4].days[5].classname%>"><a href="#"><span><%=weeks[4].days[5].text%></span></a></td>', '<td class="<%=weeks[4].days[6].classname%>"><a href="#"><span><%=weeks[4].days[6].text%></span></a></td>', '</tr>', '<tr>', '<td class="<%=weeks[5].days[0].classname%>"><a href="#"><span><%=weeks[5].days[0].text%></span></a></td>', '<td class="<%=weeks[5].days[1].classname%>"><a href="#"><span><%=weeks[5].days[1].text%></span></a></td>', '<td class="<%=weeks[5].days[2].classname%>"><a href="#"><span><%=weeks[5].days[2].text%></span></a></td>', '<td class="<%=weeks[5].days[3].classname%>"><a href="#"><span><%=weeks[5].days[3].text%></span></a></td>', '<td class="<%=weeks[5].days[4].classname%>"><a href="#"><span><%=weeks[5].days[4].text%></span></a></td>', '<td class="<%=weeks[5].days[5].classname%>"><a href="#"><span><%=weeks[5].days[5].text%></span></a></td>', '<td class="<%=weeks[5].days[6].classname%>"><a href="#"><span><%=weeks[5].days[6].text%></span></a></td>', '</tr>', '</tbody>', ], months: [ '<tbody class="<%=className%>">', '<tr>', '<td colspan="2"><a href="#"><span><%=data[0]%></span></a></td>', '<td colspan="2"><a href="#"><span><%=data[1]%></span></a></td>', '<td colspan="2"><a href="#"><span><%=data[2]%></span></a></td>', '<td colspan="1"><a href="#"><span><%=data[3]%></span></a></td>', '</tr>', '<tr>', '<td colspan="2"><a href="#"><span><%=data[4]%></span></a></td>', '<td colspan="2"><a href="#"><span><%=data[5]%></span></a></td>', '<td colspan="2"><a href="#"><span><%=data[6]%></span></a></td>', '<td colspan="1"><a href="#"><span><%=data[7]%></span></a></td>', '</tr>', '<tr>', '<td colspan="2"><a href="#"><span><%=data[8]%></span></a></td>', '<td colspan="2"><a href="#"><span><%=data[9]%></span></a></td>', '<td colspan="2"><a href="#"><span><%=data[10]%></span></a></td>', '<td colspan="1"><a href="#"><span><%=data[11]%></span></a></td>', '</tr>', '</tbody>', ], }, defaults = { /** * The currently selected date(s). This can be: a single date, an array * of two dates (sets a range when 'mode' is 'range'), or an array of * any number of dates (selects all dates when 'mode' is 'multiple'. * The supplied dates can be any one of: Date object, milliseconds * (as from date.getTime(), date.valueOf()), or a date string * parseable by Date.parse(). */ date: null, /** * Optional date which determines the current calendar month/year. This * can be one of: Date object, milliseconds (as from date.getTime(), date.valueOf()), or a date string * parseable by Date.parse(). Defaults to todays date. */ current: null, /** * true causes the datepicker calendar to be appended to the DatePicker * element and rendered, false binds the DatePicker to an event on the trigger element */ inline: false, /** * Date selection mode, one of 'single', 'range' or 'multiple'. Default * 'single'. 'Single' allows the selection of a single date, 'range' * allows the selection of range of dates, and 'multiple' allows the * selection of any number of individual dates. */ mode: 'single', /** * Number of side-by-side calendars, defaults to 1. */ calendars: 1, /** * The day that starts the week, where 0: Sunday, 1: Monday, 2: Tuesday, 3: Wednesday, 4: Thursday, 5: Friday, 6: Saturday. Defaults to Sunday */ starts: 0, /** * Previous link text. Default '&#9664;' (Unicode left arrow) */ prev: '&#9664;', /** * Next link text. Default '&#9664;' (Unicode left arrow) */ next: '&#9654;', /** * Initial calendar view, one of 'days', 'months' or 'years'. Defaults to 'days'. */ view: 'days', /** * Date picker's position relative to the trigger element (non inline * mode only), one of 'top', 'left', 'right' or 'bottom'. Defaults to 'bottom' */ position: 'bottom', /** * The trigger event used to show a non-inline calendar. Defaults to * 'focus' which is useful when the trigger element is a text input, * can also be 'click' for instance if the trigger element is a button * or some text element. */ showOn: 'focus', /** * Callback, invoked prior to the rendering of each date cell, which * allows control of the styling of the cell via the returned hash. * * @param HTMLDivElement el the datepicker containing element, ie the * div with class 'datepicker' * @param Date date the date that will be rendered * @return hash with the following optional attributes: * selected: if true, date will be selected * disabled: if true, date cell will be disabled * className: css class name to add to the cell */ onRenderCell: function () { return {}; }, /* * Callback, invoked when a date is selected, with 'this' referring to * the HTMLElement that DatePicker was invoked upon. * * @param dates: Selected date(s) depending on calendar mode. When calendar mode is 'single' this * is a single Date object. When calendar mode is 'range', this is an array containing * a 'from' and 'to' Date objects. When calendar mode is 'multiple' this is an array * of Date objects. * @param HTMLElement el the DatePicker element, ie the element that DatePicker was invoked upon */ onChange: function () {}, /** * Invoked before a non-inline datepicker is shown, with 'this' * referring to the HTMLElement that DatePicker was invoked upon, ie * the trigger element * * @param HTMLDivElement el The datepicker container element, ie the div with class 'datepicker' * @return true to allow the datepicker to be shown, false to keep it hidden */ onBeforeShow: function () { return true; }, /** * Invoked after a non-inline datepicker is shown, with 'this' * referring to the HTMLElement that DatePicker was invoked upon, ie * the trigger element * * @param HTMLDivElement el The datepicker container element, ie the div with class 'datepicker' */ onAfterShow: function () {}, /** * Invoked before a non-inline datepicker is hidden, with 'this' * referring to the HTMLElement that DatePicker was invoked upon, ie * the trigger element * * @param HTMLDivElement el The datepicker container element, ie the div with class 'datepicker' * @return true to allow the datepicker to be hidden, false to keep it visible */ onBeforeHide: function () { return true; }, /** * Invoked after a non-inline datepicker is hidden, with 'this' * referring to the HTMLElement that DatePicker was invoked upon, ie * the trigger element * * @param HTMLDivElement el The datepicker container element, ie the div with class 'datepicker' */ onAfterHide: function () {}, /** * Locale text for day/month names: provide a hash with keys 'daysMin', 'months' and 'monthsShort'. Default english */ locale: { daysMin: ['S', 'M', 'T', 'W', 'T', 'F', 'S'], months: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ], monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], }, /** * The combined height from the top/bottom borders. 'false' is the default * and generally the correct value. */ extraHeight: false, /** * The combined width from the left/right borders. 'false' is the default * and generally the correct value. */ extraWidth: false, /** * Private option, used to determine when a range is selected */ lastSel: false, /** * Prevent selection of date that after the current day */ preventFuture: false, /** * Disable selection of date that are not in the displayed month */ disableNotInMonth: true, /** * Disable selection of date that are before this date */ minDate: null, }, /** * Internal method which renders the calendar cells * * @param HTMLDivElement el datepicker container element */ fill = function (el) { var options = $(el).data('datepicker'); var cal = $(el); var currentCal = Math.floor(options.calendars / 2), date, data, dow, month, cnt = 0, days, indic, indic2, html, tblCal; cal.find('td>table tbody').remove(); for (var i = 0; i < options.calendars; i++) { date = new Date(options.current); date.addMonths(-currentCal + i); tblCal = cal.find('table').eq(i + 1); if (i == 0) tblCal.addClass('datepickerFirstView'); if (i == options.calendars - 1) tblCal.addClass('datepickerLastView'); if (tblCal.hasClass('datepickerViewDays')) { dow = date.getMonthName(true) + ', ' + date.getFullYear(); } else if (tblCal.hasClass('datepickerViewMonths')) { dow = date.getFullYear(); } else if (tblCal.hasClass('datepickerViewYears')) { dow = date.getFullYear() - 6 + ' - ' + (date.getFullYear() + 5); } tblCal.find('thead tr:first th a:eq(1) span').text(dow); dow = date.getFullYear() - 6; data = { data: [], className: 'datepickerYears', }; for (var j = 0; j < 12; j++) { data.data.push(dow + j); } // datepickerYears template html = tmpl(tpl.months.join(''), data); date.setDate(1); data = {weeks: [], test: 10}; month = date.getMonth(); var dow = (date.getDay() - options.starts) % 7; date.addDays(-(dow + (dow < 0 ? 7 : 0))); cnt = 0; while (cnt < 42) { indic = parseInt(cnt / 7, 10); indic2 = cnt % 7; if (!data.weeks[indic]) { data.weeks[indic] = { days: [], }; } data.weeks[indic].days[indic2] = { text: date.getDate(), classname: [], }; var today = new Date(); if ( today.getDate() == date.getDate() && today.getMonth() == date.getMonth() && today.getYear() == date.getYear() ) { data.weeks[indic].days[indic2].classname.push('datepickerToday'); } else if (date > today) { // current month, date in future data.weeks[indic].days[indic2].classname.push('datepickerFuture'); if (options.preventFuture === true) { // disable clicking of the date in future cells data.weeks[indic].days[indic2].classname.push('datepickerDisabled'); } } if (options.minDate && date < options.minDate) { data.weeks[indic].days[indic2].classname.push('datepickerDisabled'); } if (month != date.getMonth()) { data.weeks[indic].days[indic2].classname.push('datepickerNotInMonth'); // disable clicking of the 'not in month' cells if (options.disableNotInMonth === true) { data.weeks[indic].days[indic2].classname.push('datepickerDisabled'); } } if (date.getDay() == 0) { data.weeks[indic].days[indic2].classname.push('datepickerSunday'); } if (date.getDay() == 6) { data.weeks[indic].days[indic2].classname.push('datepickerSaturday'); } var fromUser = options.onRenderCell(el, date); var val = date.valueOf(); if (options.date && (!$.isArray(options.date) || options.date.length > 0)) { if ( fromUser.selected || options.date == val || $.inArray(val, options.date) > -1 || (options.mode == 'range' && val >= options.date[0] && val <= options.date[1]) ) { data.weeks[indic].days[indic2].classname.push('datepickerSelected'); } } if (fromUser.disabled) { data.weeks[indic].days[indic2].classname.push('datepickerDisabled'); } if (fromUser.className) { data.weeks[indic].days[indic2].classname.push(fromUser.className); } data.weeks[indic].days[indic2].classname = data.weeks[indic].days[indic2].classname.join(' '); cnt++; date.addDays(1); } // Fill the datepickerDays template with data html = tmpl(tpl.days.join(''), data) + html; data = { data: options.locale.monthsShort, className: 'datepickerMonths', }; // datepickerMonths template html = tmpl(tpl.months.join(''), data) + html; tblCal.append(html); } }, /** * Extends the Date object with some useful helper methods */ extendDate = function (locale) { if (Date.prototype.tempDate) { return; } Date.prototype.tempDate = null; Date.prototype.months = locale.months; Date.prototype.monthsShort = locale.monthsShort; Date.prototype.getMonthName = function (fullName) { return this[fullName ? 'months' : 'monthsShort'][this.getMonth()]; }; Date.prototype.addDays = function (n) { this.setDate(this.getDate() + n); this.tempDate = this.getDate(); }; Date.prototype.addMonths = function (n) { if (this.tempDate == null) { this.tempDate = this.getDate(); } this.setDate(1); this.setMonth(this.getMonth() + n); this.setDate(Math.min(this.tempDate, this.getMaxDays())); }; Date.prototype.addYears = function (n) { if (this.tempDate == null) { this.tempDate = this.getDate(); } this.setDate(1); this.setFullYear(this.getFullYear() + n); this.setDate(Math.min(this.tempDate, this.getMaxDays())); }; Date.prototype.getMaxDays = function () { var tmpDate = new Date(Date.parse(this)), d = 28, m; m = tmpDate.getMonth(); d = 28; while (tmpDate.getMonth() == m) { d++; tmpDate.setDate(d); } return d - 1; }; Date.prototype.isSameMonthAs = function (pDate) { return this.getFullYear() === pDate.getFullYear() && this.getMonth() === pDate.getMonth(); }; }, /** * Internal method which lays out the calendar widget */ layout = function (el) { var options = $(el).data('datepicker'); var cal = $('#' + options.id); if (options.extraHeight === false) { var divs = $(el).find('div'); options.extraHeight = divs.get(0).offsetHeight + divs.get(1).offsetHeight; // heights from top/bottom borders options.extraWidth = divs.get(2).offsetWidth + divs.get(3).offsetWidth; // widths from left/right borders } var tbl = cal.find('table:first').get(0); var width = tbl.offsetWidth; var height = tbl.offsetHeight; cal.css({ width: width + options.extraWidth + 'px', height: height + options.extraHeight + 'px', }) .find('div.datepickerContainer') .css({ width: width + 'px', height: height + 'px', }); }, /** * Internal method, bound to the HTML DatePicker Element, onClick. * This is the function that controls the behavior of the calendar when * the title, next/previous, or a date cell is clicked on. */ click = function (ev) { if ($(ev.target).is('span')) { ev.target = ev.target.parentNode; } var el = $(ev.target); if (el.is('a')) { ev.target.blur(); if (el.hasClass('datepickerDisabled')) { return false; } var options = $(this).data('datepicker'); var parentEl = el.parent(); var tblEl = parentEl.parent().parent().parent(); var tblIndex = $('table', this).index(tblEl.get(0)) - 1; var tmp = new Date(options.current); var changed = false; var fillIt = false; var currentCal = Math.floor(options.calendars / 2); if (parentEl.is('th')) { // clicking the calendar title if (el.hasClass('datepickerMonth')) { // clicking on the title of a Month Datepicker tmp.addMonths(tblIndex - currentCal); if (options.mode == 'range') { // range, select the whole month tmp.setDate(1); options.date[0] = tmp.setHours(0, 0, 0, 0).valueOf(); tmp.addDays(tmp.getMaxDays() - 1); tmp.setHours(23, 59, 59, 0); options.date[1] = tmp.valueOf(); fillIt = true; changed = true; options.lastSel = false; } else if (options.calendars == 1) { // single/multiple mode with a single calendar: swap between daily/monthly/yearly view. // Note: there's no reason a multi-calendar widget can't have this functionality, // however I think it looks really unintuitive. if (tblEl.eq(0).hasClass('datepickerViewDays')) { tblEl.eq(0).toggleClass('datepickerViewDays datepickerViewMonths'); el.find('span').text(tmp.getFullYear()); } else if (tblEl.eq(0).hasClass('datepickerViewMonths')) { tblEl.eq(0).toggleClass('datepickerViewMonths datepickerViewYears'); el.find('span').text(tmp.getFullYear() - 6 + ' - ' + (tmp.getFullYear() + 5)); } else if (tblEl.eq(0).hasClass('datepickerViewYears')) { tblEl.eq(0).toggleClass('datepickerViewYears datepickerViewDays'); el.find('span').text(tmp.getMonthName(true) + ', ' + tmp.getFullYear()); } } } else if (parentEl.parent().parent().is('thead')) { // clicked either next/previous arrows if (tblEl.eq(0).hasClass('datepickerViewDays')) { options.current.addMonths(el.hasClass('datepickerGoPrev') ? -1 : 1); } else if (tblEl.eq(0).hasClass('datepickerViewMonths')) { options.current.addYears(el.hasClass('datepickerGoPrev') ? -1 : 1); } else if (tblEl.eq(0).hasClass('datepickerViewYears')) { options.current.addYears(el.hasClass('datepickerGoPrev') ? -12 : 12); } fillIt = true; } } else if (parentEl.is('td') && !parentEl.hasClass('datepickerDisabled')) { // clicking the calendar grid if (tblEl.eq(0).hasClass('datepickerViewMonths')) { // clicked a month cell options.current.setMonth(tblEl.find('tbody.datepickerMonths td').index(parentEl)); options.current.setFullYear( parseInt(tblEl.find('thead th a.datepickerMonth span').text(), 10) ); options.current.addMonths(currentCal - tblIndex); tblEl.eq(0).toggleClass('datepickerViewMonths datepickerViewDays'); } else if (tblEl.eq(0).hasClass('datepickerViewYears')) { // clicked a year cell options.current.setFullYear(parseInt(el.text(), 10)); tblEl.eq(0).toggleClass('datepickerViewYears datepickerViewMonths'); } else { // clicked a day cell var val = parseInt(el.text(), 10); tmp.addMonths(tblIndex - currentCal); if (parentEl.hasClass('datepickerNotInMonth')) { tmp.addMonths(val > 15 ? -1 : 1); } tmp.setDate(val); switch (options.mode) { case 'multiple': val = tmp.setHours(0, 0, 0, 0).valueOf(); if ($.inArray(val, options.date) > -1) { $.each(options.date, function (nr, dat) { if (dat == val) { options.date.splice(nr, 1); return false; } }); } else { options.date.push(val); } break; case 'range': if (!options.lastSel) { // first click: set to the start of the day options.date[0] = tmp.setHours(0, 0, 0, 0).valueOf(); } // get the very end of the day clicked val = tmp.setHours(23, 59, 59, 0).valueOf(); if (val < options.date[0]) { // second range click < first options.date[1] = options.date[0] + 86399000; // starting date + 1 day options.date[0] = val - 86399000; // minus 1 day } else { // initial range click, or final range click >= first options.date[1] = val; } options.lastSel = !options.lastSel; break; default: options.date = tmp.valueOf(); break; } changed = true; } fillIt = true; } if (fillIt) { fill(this); } if (changed) { options.onChange.apply(this, prepareDate(options)); } } return false; }, /** * Internal method, called from the public getDate() method, and when * invoking the onChange callback function * * @param object options with the following attributes: 'mode' which can * be one of 'single', 'range', or 'multiple'. Attribute 'date' * which will be a single timestamp when 'mode' is 'single', or * an array of timestamps otherwise. Attribute 'el' which is the * HTML element that DatePicker was invoked upon. * @return array where the first item is either a Date object, or an * array of Date objects, depending on the DatePicker mode, and * the second item is the HTMLElement that DatePicker was invoked * upon. */ prepareDate = function (options) { var dates = null; if (options.mode == 'single') { if (options.date) dates = new Date(options.date); } else { dates = new Array(); $(options.date).each(function (i, val) { dates.push(new Date(val)); }); } return [dates, options.el]; }, /** * Internal method, returns an object containing the viewport dimensions */ getViewport = function () { var m = document.compatMode == 'CSS1Compat'; return { l: window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft), t: window.pageYOffset || (m ? document.documentElement.scrollTop : document.body.scrollTop), w: window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth), h: window.innerHeight || (m ? document.documentElement.clientHeight : document.body.clientHeight), }; }, /** * Internal method, returns true if el is a child of parentEl */ isChildOf = function (parentEl, el, container) { if (parentEl == el) { return true; } if (parentEl.contains) { return parentEl.contains(el); } if (parentEl.compareDocumentPosition) { return !!(parentEl.compareDocumentPosition(el) & 16); } var prEl = el.parentNode; while (prEl && prEl != container) { if (prEl == parentEl) return true; prEl = prEl.parentNode; } return false; }, /** * Bound to the HTML DatePicker element when it's not inline, and also * can be called directly to show the bound datepicker. A DatePicker * calendar shown with this method will hide on a mouseclick outside * of the calendar. * * Method is not applicable for inline DatePickers */ show = function (ev) { var cal = $('#' + $(this).data('datepickerId')); if (!cal.is(':visible')) { var calEl = cal.get(0); var options = cal.data('datepicker'); var test = options.onBeforeShow.apply(this, [calEl]); if (options.onBeforeShow.apply(this, [calEl]) == false) { return; } fill(calEl); var pos = $(this).offset(); var viewPort = getViewport(); var top = pos.top; var left = pos.left; var oldDisplay = $.css(calEl, 'display'); cal.css({ visibility: 'hidden', display: 'block', }); layout(calEl); switch (options.position) { case 'top': top -= calEl.offsetHeight; break; case 'left': left -= calEl.offsetWidth; break; case 'right': left += this.offsetWidth; break; case 'bottom': top += this.offsetHeight; break; } if (top + calEl.offsetHeight > viewPort.t + viewPort.h) { top = pos.top - calEl.offsetHeight; } if (top < viewPort.t) { top = pos.top + this.offsetHeight + calEl.offsetHeight; } if (left + calEl.offsetWidth > viewPort.l + viewPort.w) { left = pos.left - calEl.offsetWidth; } if (left < viewPort.l) { left = pos.left + this.offsetWidth; } cal.css({ visibility: 'visible', display: 'block', top: top + 'px', left: left + 'px', }); options.onAfterShow.apply(this, [cal.get(0)]); $(document).bind('mousedown', {cal: cal, trigger: this}, hide); // global listener so clicking outside the calendar will close it } return false; }, /** * Hide a non-inline DatePicker calendar. * * Not applicable for inline DatePickers. * * @param ev Event object */ hide = function (ev) { if (ev.target != ev.data.trigger && !isChildOf(ev.data.cal.get(0), ev.target, ev.data.cal.get(0))) { if (ev.data.cal.data('datepicker').onBeforeHide.apply(this, [ev.data.cal.get(0)]) != false) { ev.data.cal.hide(); ev.data.cal.data('datepicker').onAfterHide.apply(this, [ev.data.cal.get(0)]); $(document).unbind('mousedown', hide); // remove the global listener } } }, /** * Internal method to normalize the selected date based on the current * calendar mode. */ normalizeDate = function (mode, date) { // if range/multi mode, make sure that the current date value is at least an empty array if (mode != 'single' && !date) date = []; // if we have a selected date and not a null or empty array if (date && (!$.isArray(date) || date.length > 0)) { // Create a standardized date depending on the calendar mode if (mode != 'single') { if (!$.isArray(date)) { date = [new Date(date).setHours(0, 0, 0, 0).valueOf()]; if (mode == 'range') { // create a range of one day date.push(new Date(date[0]).setHours(23, 59, 59, 0).valueOf()); } } else { for (var i = 0; i < date.length; i++) { date[i] = new Date(date[i]).setHours(0, 0, 0, 0).valueOf(); } if (mode == 'range') { // for range mode, create the other end of the range if (date.length == 1) date.push(new Date(date[0])); date[1] = new Date(date[1]).setHours(23, 59, 59, 0).valueOf(); } } } else { // mode is single, convert date object into a timestamp date = new Date(date).setHours(0, 0, 0, 0).valueOf(); } // at this point date is either a timestamp at hour zero // for 'single' mode, an array of timestamps at hour zero for // 'multiple' mode, or a two-item array with timestamps at hour // zero and hour 23:59 for 'range' mode } return date; }; return { /** * 'Public' functions */ /** * Called when element.DatePicker() is invoked * * Note that 'this' is the HTML element that DatePicker was invoked upon * @see DatePicker() */ init: function (options) { options = $.extend({}, defaults, options || {}); extendDate(options.locale); options.calendars = Math.max(1, parseInt(options.calendars, 10) || 1); options.mode = /single|multiple|range/.test(options.mode) ? options.mode : 'single'; return this.each(function () { if (!$(this).data('datepicker')) { options.el = this; options.date = normalizeDate(options.mode, options.date); if (!options.current) { options.current = new Date(); } else { options.current = new Date(options.current); } options.current.setDate(1); options.current.setHours(0, 0, 0, 0); var id = 'datepicker_' + parseInt(Math.random() * 1000), cnt; options.id = id; $(this).data('datepickerId', options.id); var cal = $(tpl.wrapper).attr('id', id).bind('click', click).data('datepicker', options); if (options.className) { cal.addClass(options.className); } var html = ''; for (var i = 0; i < options.calendars; i++) { cnt = options.starts; if (i > 0) { html += tpl.space; } // calendar header template html += tmpl(tpl.head.join(''), { prev: options.prev, next: options.next, day1: options.locale.daysMin[cnt++ % 7], day2: options.locale.daysMin[cnt++ % 7], day3: options.locale.daysMin[cnt++ % 7], day4: options.locale.daysMin[cnt++ % 7], day5: options.locale.daysMin[cnt++ % 7], day6: options.locale.daysMin[cnt++ % 7], day7: options.locale.daysMin[cnt++ % 7], }); } cal.find('tr:first').append(html).find('table').addClass(views[options.view]); fill(cal.get(0)); if (options.inline) { cal.appendTo(this).show().css('position', 'relative'); layout(cal.get(0)); } else { cal.appendTo(document.body); $(this).bind(options.showOn, show); } } }); }, /** * Shows the DatePicker, applicable only when the picker is not inline * * @return the DatePicker HTML element * @see DatePickerShow() */ showPicker: function () { return this.each(function () { if ($(this).data('datepickerId')) { var cal = $('#' + $(this).data('datepickerId')); var options = cal.data('datepicker'); if (!options.inline) { show.apply(this); } } }); }, /** * Hides the DatePicker, applicable only when the picker is not inline * * @return the DatePicker HTML element * @see DatePickerHide() */ hidePicker: function () { return this.each(function () { if ($(this).data('datepickerId')) { var cal = $('#' + $(this).data('datepickerId')); var options = cal.data('datepicker'); if (!options.inline) { $('#' + $(this).data('datepickerId')).hide(); } } }); }, /** * Sets the DatePicker current date, and optionally shifts the current * calendar to that